|
1 | 1 | package services |
2 | 2 |
|
3 | 3 | import ( |
| 4 | + "encoding/json" |
4 | 5 | "testing" |
5 | 6 | "time" |
6 | 7 |
|
| 8 | + "gorm.io/datatypes" |
| 9 | + |
7 | 10 | "github.com/openshift-hyperfleet/hyperfleet-api/pkg/api" |
8 | 11 | ) |
9 | 12 |
|
| 13 | +// makeConditionsJSON marshals a slice of {Type, Status} pairs into datatypes.JSON. |
| 14 | +func makeConditionsJSON(t *testing.T, conditions []struct{ Type, Status string }) datatypes.JSON { |
| 15 | + t.Helper() |
| 16 | + b, err := json.Marshal(conditions) |
| 17 | + if err != nil { |
| 18 | + t.Fatalf("failed to marshal conditions: %v", err) |
| 19 | + } |
| 20 | + return datatypes.JSON(b) |
| 21 | +} |
| 22 | + |
| 23 | +// makeAdapterStatus builds an AdapterStatus with the given fields. |
| 24 | +func makeAdapterStatus(adapter string, gen int32, lastReportTime *time.Time, conditionsJSON datatypes.JSON) *api.AdapterStatus { |
| 25 | + return &api.AdapterStatus{ |
| 26 | + Adapter: adapter, |
| 27 | + ObservedGeneration: gen, |
| 28 | + LastReportTime: lastReportTime, |
| 29 | + Conditions: conditionsJSON, |
| 30 | + } |
| 31 | +} |
| 32 | + |
| 33 | +func ptr(t time.Time) *time.Time { return &t } |
| 34 | + |
| 35 | +func TestComputeReadyLastUpdated_NotReady(t *testing.T) { |
| 36 | + now := time.Now() |
| 37 | + // When isReady=false the function must return now regardless of adapter state. |
| 38 | + result := computeReadyLastUpdated(nil, []string{"dns"}, 1, now, false) |
| 39 | + if !result.Equal(now) { |
| 40 | + t.Errorf("expected now, got %v", result) |
| 41 | + } |
| 42 | +} |
| 43 | + |
| 44 | +func TestComputeReadyLastUpdated_MissingAdapter(t *testing.T) { |
| 45 | + now := time.Now() |
| 46 | + statuses := api.AdapterStatusList{ |
| 47 | + makeAdapterStatus("validator", 1, ptr(now.Add(-5*time.Second)), makeConditionsJSON(t, []struct{ Type, Status string }{ |
| 48 | + {api.ConditionTypeAvailable, "True"}, |
| 49 | + })), |
| 50 | + } |
| 51 | + // "dns" is required but not in the list → safety fallback to now. |
| 52 | + result := computeReadyLastUpdated(statuses, []string{"validator", "dns"}, 1, now, true) |
| 53 | + if !result.Equal(now) { |
| 54 | + t.Errorf("expected now (missing adapter), got %v", result) |
| 55 | + } |
| 56 | +} |
| 57 | + |
| 58 | +func TestComputeReadyLastUpdated_NilLastReportTime(t *testing.T) { |
| 59 | + now := time.Now() |
| 60 | + statuses := api.AdapterStatusList{ |
| 61 | + makeAdapterStatus("dns", 1, nil, makeConditionsJSON(t, []struct{ Type, Status string }{ |
| 62 | + {api.ConditionTypeAvailable, "True"}, |
| 63 | + })), |
| 64 | + } |
| 65 | + result := computeReadyLastUpdated(statuses, []string{"dns"}, 1, now, true) |
| 66 | + if !result.Equal(now) { |
| 67 | + t.Errorf("expected now (nil LastReportTime), got %v", result) |
| 68 | + } |
| 69 | +} |
| 70 | + |
| 71 | +func TestComputeReadyLastUpdated_WrongGeneration(t *testing.T) { |
| 72 | + now := time.Now() |
| 73 | + reportTime := now.Add(-10 * time.Second) |
| 74 | + statuses := api.AdapterStatusList{ |
| 75 | + // ObservedGeneration=1 but resourceGeneration=2 — skipped. |
| 76 | + makeAdapterStatus("dns", 1, ptr(reportTime), makeConditionsJSON(t, []struct{ Type, Status string }{ |
| 77 | + {api.ConditionTypeAvailable, "True"}, |
| 78 | + })), |
| 79 | + } |
| 80 | + // All adapters skipped → minTime is nil → fallback to now. |
| 81 | + result := computeReadyLastUpdated(statuses, []string{"dns"}, 2, now, true) |
| 82 | + if !result.Equal(now) { |
| 83 | + t.Errorf("expected now (wrong generation), got %v", result) |
| 84 | + } |
| 85 | +} |
| 86 | + |
| 87 | +func TestComputeReadyLastUpdated_AvailableFalse(t *testing.T) { |
| 88 | + now := time.Now() |
| 89 | + reportTime := now.Add(-10 * time.Second) |
| 90 | + statuses := api.AdapterStatusList{ |
| 91 | + makeAdapterStatus("dns", 1, ptr(reportTime), makeConditionsJSON(t, []struct{ Type, Status string }{ |
| 92 | + {api.ConditionTypeAvailable, "False"}, |
| 93 | + })), |
| 94 | + } |
| 95 | + // Available=False → skipped → fallback to now. |
| 96 | + result := computeReadyLastUpdated(statuses, []string{"dns"}, 1, now, true) |
| 97 | + if !result.Equal(now) { |
| 98 | + t.Errorf("expected now (Available=False), got %v", result) |
| 99 | + } |
| 100 | +} |
| 101 | + |
| 102 | +func TestComputeReadyLastUpdated_SingleQualifyingAdapter(t *testing.T) { |
| 103 | + now := time.Now() |
| 104 | + reportTime := now.Add(-30 * time.Second) |
| 105 | + statuses := api.AdapterStatusList{ |
| 106 | + makeAdapterStatus("dns", 1, ptr(reportTime), makeConditionsJSON(t, []struct{ Type, Status string }{ |
| 107 | + {api.ConditionTypeAvailable, "True"}, |
| 108 | + })), |
| 109 | + } |
| 110 | + result := computeReadyLastUpdated(statuses, []string{"dns"}, 1, now, true) |
| 111 | + if !result.Equal(reportTime) { |
| 112 | + t.Errorf("expected %v, got %v", reportTime, result) |
| 113 | + } |
| 114 | +} |
| 115 | + |
| 116 | +func TestComputeReadyLastUpdated_MultipleAdapters_ReturnsMinimum(t *testing.T) { |
| 117 | + now := time.Now() |
| 118 | + older := now.Add(-60 * time.Second) |
| 119 | + newer := now.Add(-10 * time.Second) |
| 120 | + |
| 121 | + statuses := api.AdapterStatusList{ |
| 122 | + makeAdapterStatus("validator", 2, ptr(newer), makeConditionsJSON(t, []struct{ Type, Status string }{ |
| 123 | + {api.ConditionTypeAvailable, "True"}, |
| 124 | + })), |
| 125 | + makeAdapterStatus("dns", 2, ptr(older), makeConditionsJSON(t, []struct{ Type, Status string }{ |
| 126 | + {api.ConditionTypeAvailable, "True"}, |
| 127 | + })), |
| 128 | + } |
| 129 | + result := computeReadyLastUpdated(statuses, []string{"validator", "dns"}, 2, now, true) |
| 130 | + if !result.Equal(older) { |
| 131 | + t.Errorf("expected minimum timestamp %v, got %v", older, result) |
| 132 | + } |
| 133 | +} |
| 134 | + |
| 135 | +func TestComputeReadyLastUpdated_MixedGenerations_OnlyCurrentGenCounts(t *testing.T) { |
| 136 | + now := time.Now() |
| 137 | + oldGenTime := now.Add(-120 * time.Second) |
| 138 | + newGenTime := now.Add(-5 * time.Second) |
| 139 | + |
| 140 | + statuses := api.AdapterStatusList{ |
| 141 | + // validator is at gen 3 (current) — qualifies. |
| 142 | + makeAdapterStatus("validator", 3, ptr(newGenTime), makeConditionsJSON(t, []struct{ Type, Status string }{ |
| 143 | + {api.ConditionTypeAvailable, "True"}, |
| 144 | + })), |
| 145 | + // dns is at gen 2 (stale) — skipped. |
| 146 | + makeAdapterStatus("dns", 2, ptr(oldGenTime), makeConditionsJSON(t, []struct{ Type, Status string }{ |
| 147 | + {api.ConditionTypeAvailable, "True"}, |
| 148 | + })), |
| 149 | + } |
| 150 | + // Only validator qualifies; dns is skipped (wrong gen), so minTime = newGenTime. |
| 151 | + result := computeReadyLastUpdated(statuses, []string{"validator", "dns"}, 3, now, true) |
| 152 | + if !result.Equal(newGenTime) { |
| 153 | + t.Errorf("expected %v (only current-gen adapter), got %v", newGenTime, result) |
| 154 | + } |
| 155 | +} |
| 156 | + |
10 | 157 | func TestMapAdapterToConditionType(t *testing.T) { |
11 | 158 | tests := []struct { |
12 | 159 | adapter string |
|
0 commit comments