dataDownloadFaultRecordCall_test.go 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137
  1. package services
  2. import (
  3. "context"
  4. "strings"
  5. "testing"
  6. "time"
  7. "fwh-toolbox-server/domain"
  8. "fwh-toolbox-server/repositories"
  9. )
  10. func TestFaultSubMatchesCallable(t *testing.T) {
  11. candidate := domain.FaultRecordCallableInfo{FailureTime: "2026-09-22 12:30:00", TriggerSourceCode: "E42"}
  12. sub := domain.FaultRecordSubInfo{FailureTime: "2026-09-22 12:30:00", EventTriggerSource: "E42"}
  13. if !faultSubMatchesCallable(sub, candidate) {
  14. t.Fatal("matching time and code should match")
  15. }
  16. sub.EventTriggerSource = "E43"
  17. if faultSubMatchesCallable(sub, candidate) {
  18. t.Fatal("different fault code must not match")
  19. }
  20. }
  21. func TestExistingFaultRecordRequiresCompleteSubList(t *testing.T) {
  22. candidates := []domain.FaultRecordCallableInfo{{FailureTime: "2026-09-22 12:30:00", TriggerSourceCode: "E42"}}
  23. subs := []domain.FaultRecordSubInfo{{Id: 10, FailureTime: candidates[0].FailureTime, EventTriggerSource: candidates[0].TriggerSourceCode}}
  24. matched := []bool{false}
  25. record := domain.FaultRecordInfo{Id: 99, FaultRecordsCount: 2}
  26. if got := matchExistingFaultRecord(record, subs, candidates, matched); len(got) != 0 || matched[0] {
  27. t.Fatalf("incomplete record was reused: %+v", got)
  28. }
  29. record.FaultRecordsCount = 1
  30. if got := matchExistingFaultRecord(record, subs, candidates, matched); len(got) != 1 || !matched[0] {
  31. t.Fatalf("complete record not reused: %+v", got)
  32. }
  33. }
  34. func TestResolveFaultRecordSourcesReusesVerifiedSubrecord(t *testing.T) {
  35. originalCatalog := repositories.QueryFaultRecordCallableList
  36. originalList := repositories.QueryFaultRecordList
  37. originalSubs := repositories.QueryFaultRecordSubList
  38. t.Cleanup(func() {
  39. repositories.QueryFaultRecordCallableList = originalCatalog
  40. repositories.QueryFaultRecordList = originalList
  41. repositories.QueryFaultRecordSubList = originalSubs
  42. })
  43. repositories.QueryFaultRecordCallableList = func(_ int, _ string) ([]domain.FaultRecordCallableInfo, error) {
  44. return []domain.FaultRecordCallableInfo{{Idx: 7, FailureTime: "2026-09-22 12:30:00", TriggerSourceCode: "E42"}}, nil
  45. }
  46. repositories.QueryFaultRecordList = func(_, _, _ string) ([]domain.FaultRecordInfo, error) {
  47. return []domain.FaultRecordInfo{{Id: 99, GatewayId: "GATE-1", DeviceType: 1, FaultRecordsCount: 1}}, nil
  48. }
  49. repositories.QueryFaultRecordSubList = func(_ int64) ([]domain.FaultRecordSubInfo, error) {
  50. return []domain.FaultRecordSubInfo{{Id: 10, FaultId: 99, GatewayId: "GATE-1", FailureTime: "2026-09-22 12:30:00", EventTriggerSource: "E42"}}, nil
  51. }
  52. start := time.Date(2026, 9, 22, 0, 0, 0, 0, time.UTC)
  53. sources, err := resolveFaultRecordSources(context.Background(), &domain.DataDownloadItem{DeviceSN: "GATE-1"}, "document-1", 1, start, start.Add(24*time.Hour-time.Second))
  54. if err != nil {
  55. t.Fatal(err)
  56. }
  57. if len(sources) != 1 || sources[0].sub.Id != 10 {
  58. t.Fatalf("sources = %+v", sources)
  59. }
  60. }
  61. func TestFindFaultCallByRequestChecksAllIdentityFields(t *testing.T) {
  62. original := repositories.QueryFaultRecordCallRecordPageContext
  63. t.Cleanup(func() { repositories.QueryFaultRecordCallRecordPageContext = original })
  64. sn := "GATE-1"
  65. other := "GATE-2"
  66. status := 0
  67. calledAt := time.Date(2026, 9, 23, 12, 0, 0, 0, time.UTC)
  68. repositories.QueryFaultRecordCallRecordPageContext = func(_ context.Context, page, size int) ([]domain.FaultRecordInfo, error) {
  69. if page > 1 {
  70. return nil, nil
  71. }
  72. return []domain.FaultRecordInfo{
  73. {Id: 101, DeviceType: 1, GatewayId: other, FaultRecordsCount: 2, TriggerType: 2, CellTime: "2026-09-23 12:00:00", Status: &status},
  74. {Id: 102, DeviceType: 1, GatewayId: sn, FaultRecordsCount: 1, TriggerType: 2, CellTime: "2026-09-23 12:00:00", Status: &status},
  75. {Id: 103, DeviceType: 1, GatewayId: sn, FaultRecordsCount: 2, TriggerType: 1, CellTime: "2026-09-23 12:00:00", Status: &status},
  76. {Id: 104, DeviceType: 1, GatewayId: sn, FaultRecordsCount: 2, TriggerType: 2, CellTime: "2026-09-23 12:02:00", Status: &status},
  77. {Id: 105, DeviceType: 1, GatewayId: sn, FaultRecordsCount: 2, TriggerType: 2, CellTime: "2026-09-23 12:00:30", Status: &status},
  78. }, nil
  79. }
  80. record, err := findFaultCallByRequest(context.Background(), sn, 1, 2, calledAt)
  81. if err != nil {
  82. t.Fatal(err)
  83. }
  84. if record == nil || record.Id != 105 {
  85. t.Fatalf("got %+v, want ID 105", record)
  86. }
  87. }
  88. func TestFindFaultCallByIDUsesPageSearch(t *testing.T) {
  89. original := repositories.QueryFaultRecordCallRecordPageContext
  90. t.Cleanup(func() { repositories.QueryFaultRecordCallRecordPageContext = original })
  91. queries := 0
  92. repositories.QueryFaultRecordCallRecordPageContext = func(_ context.Context, page, size int) ([]domain.FaultRecordInfo, error) {
  93. queries++
  94. if page > 128 {
  95. return nil, nil
  96. }
  97. rows := make([]domain.FaultRecordInfo, size)
  98. for i := range rows {
  99. rows[i].Id = int64(6401 - (page-1)*size - i)
  100. }
  101. return rows, nil
  102. }
  103. record, err := findFaultCallByID(context.Background(), 1600)
  104. if err != nil {
  105. t.Fatal(err)
  106. }
  107. if record == nil || record.Id != 1600 {
  108. t.Fatalf("got %+v, want ID 1600", record)
  109. }
  110. if queries > 16 {
  111. t.Fatalf("used %d queries for a record on page 97", queries)
  112. }
  113. }
  114. func TestSummonFaultRecordsHonorsElapsedTimeout(t *testing.T) {
  115. started := time.Now().UTC().Add(-faultCallTimeout - time.Minute)
  116. item := &domain.DataDownloadItem{CallRequestedAt: &started, DeviceSN: "GATE-1"}
  117. _, err := summonFaultRecords(context.Background(), item, "document-1", 1, []int{1})
  118. if err == nil || !strings.Contains(err.Error(), "timed out after 2h") {
  119. t.Fatalf("got %v, want timeout", err)
  120. }
  121. }
  122. func TestActiveFaultCallKeepsOwnershipAfterCancellation(t *testing.T) {
  123. started := time.Now().UTC().Add(-time.Minute)
  124. item := &domain.DataDownloadItem{Kind: domain.DataDownloadFaultRecord, CallRequestedAt: &started}
  125. if err := ReleaseFaultCallOwnership(context.Background(), item); err != nil {
  126. t.Fatal(err)
  127. }
  128. }