|
| 1 | +package response |
| 2 | + |
| 3 | +import ( |
| 4 | + "encoding/json" |
| 5 | + "testing" |
| 6 | + |
| 7 | + "github.com/stretchr/testify/assert" |
| 8 | + "github.com/stretchr/testify/require" |
| 9 | +) |
| 10 | + |
| 11 | +func TestFlatten(t *testing.T) { |
| 12 | + tests := []struct { |
| 13 | + name string |
| 14 | + input map[string]any |
| 15 | + expected map[string]any |
| 16 | + }{ |
| 17 | + { |
| 18 | + name: "promotes primitive fields from nested map", |
| 19 | + input: map[string]any{ |
| 20 | + "title": "fix bug", |
| 21 | + "user": map[string]any{ |
| 22 | + "login": "user", |
| 23 | + "id": float64(1), |
| 24 | + }, |
| 25 | + }, |
| 26 | + expected: map[string]any{ |
| 27 | + "title": "fix bug", |
| 28 | + "user.login": "user", |
| 29 | + "user.id": float64(1), |
| 30 | + }, |
| 31 | + }, |
| 32 | + { |
| 33 | + name: "discards non-primitive nested values", |
| 34 | + input: map[string]any{ |
| 35 | + "user": map[string]any{ |
| 36 | + "login": "user", |
| 37 | + "repos": []any{"repo1"}, |
| 38 | + "org": map[string]any{"name": "org"}, |
| 39 | + }, |
| 40 | + }, |
| 41 | + expected: map[string]any{ |
| 42 | + "user.login": "user", |
| 43 | + }, |
| 44 | + }, |
| 45 | + } |
| 46 | + |
| 47 | + for _, tc := range tests { |
| 48 | + t.Run(tc.name, func(t *testing.T) { |
| 49 | + result := flatten(tc.input) |
| 50 | + assert.Equal(t, tc.expected, result) |
| 51 | + }) |
| 52 | + } |
| 53 | +} |
| 54 | + |
| 55 | +func TestTrimArrayFields(t *testing.T) { |
| 56 | + original := collectionFieldExtractors |
| 57 | + defer func() { collectionFieldExtractors = original }() |
| 58 | + |
| 59 | + collectionFieldExtractors = map[string][]string{ |
| 60 | + "reviewers": {"login", "state"}, |
| 61 | + } |
| 62 | + |
| 63 | + result := optimizeItem(map[string]any{ |
| 64 | + "reviewers": []any{ |
| 65 | + map[string]any{"login": "alice", "state": "approved", "id": float64(1)}, |
| 66 | + map[string]any{"login": "bob", "state": "changes_requested", "id": float64(2)}, |
| 67 | + }, |
| 68 | + "title": "Fix bug", |
| 69 | + }) |
| 70 | + |
| 71 | + expected := []any{ |
| 72 | + map[string]any{"login": "alice", "state": "approved"}, |
| 73 | + map[string]any{"login": "bob", "state": "changes_requested"}, |
| 74 | + } |
| 75 | + assert.Equal(t, expected, result["reviewers"]) |
| 76 | + assert.Equal(t, "Fix bug", result["title"]) |
| 77 | +} |
| 78 | + |
| 79 | +func TestFilterByFillRate(t *testing.T) { |
| 80 | + items := []map[string]any{ |
| 81 | + {"title": "a", "body": "text", "milestone": "v1"}, |
| 82 | + {"title": "b", "body": "text"}, |
| 83 | + {"title": "c", "body": "text"}, |
| 84 | + {"title": "d", "body": "text"}, |
| 85 | + {"title": "e", "body": "text"}, |
| 86 | + {"title": "f", "body": "text"}, |
| 87 | + {"title": "g", "body": "text"}, |
| 88 | + {"title": "h", "body": "text"}, |
| 89 | + {"title": "i", "body": "text"}, |
| 90 | + {"title": "j", "body": "text"}, |
| 91 | + } |
| 92 | + |
| 93 | + result := filterByFillRate(items, 0.1) |
| 94 | + |
| 95 | + for _, item := range result { |
| 96 | + assert.Contains(t, item, "title") |
| 97 | + assert.Contains(t, item, "body") |
| 98 | + assert.NotContains(t, item, "milestone") |
| 99 | + } |
| 100 | +} |
| 101 | + |
| 102 | +func TestFilterByFillRate_PreservesFields(t *testing.T) { |
| 103 | + original := preservedFields |
| 104 | + defer func() { preservedFields = original }() |
| 105 | + |
| 106 | + preservedFields = map[string]bool{"html_url": true} |
| 107 | + |
| 108 | + items := make([]map[string]any, 10) |
| 109 | + for i := range items { |
| 110 | + items[i] = map[string]any{"title": "x"} |
| 111 | + } |
| 112 | + items[0]["html_url"] = "https://github.com/repo/1" |
| 113 | + |
| 114 | + result := filterByFillRate(items, 0.1) |
| 115 | + assert.Contains(t, result[0], "html_url") |
| 116 | +} |
| 117 | + |
| 118 | +func TestOptimizeItems(t *testing.T) { |
| 119 | + tests := []struct { |
| 120 | + name string |
| 121 | + items []map[string]any |
| 122 | + expected []map[string]any |
| 123 | + }{ |
| 124 | + { |
| 125 | + name: "applies all strategies in sequence", |
| 126 | + items: []map[string]any{ |
| 127 | + { |
| 128 | + "title": "Fix bug", |
| 129 | + "body": "line1\n\nline2", |
| 130 | + "url": "https://api.github.com/repos/1", |
| 131 | + "html_url": "https://github.com/repo/1", |
| 132 | + "avatar_url": "https://avatars.githubusercontent.com/1", |
| 133 | + "draft": false, |
| 134 | + "merged_at": nil, |
| 135 | + "labels": []any{map[string]any{"name": "bug"}}, |
| 136 | + "user": map[string]any{ |
| 137 | + "login": "user", |
| 138 | + "avatar_url": "https://avatars.githubusercontent.com/1", |
| 139 | + }, |
| 140 | + }, |
| 141 | + }, |
| 142 | + expected: []map[string]any{ |
| 143 | + { |
| 144 | + "title": "Fix bug", |
| 145 | + "body": "line1 line2", |
| 146 | + "html_url": "https://github.com/repo/1", |
| 147 | + "labels": "bug", |
| 148 | + "user.login": "user", |
| 149 | + }, |
| 150 | + }, |
| 151 | + }, |
| 152 | + { |
| 153 | + name: "nil input", |
| 154 | + items: nil, |
| 155 | + expected: nil, |
| 156 | + }, |
| 157 | + } |
| 158 | + |
| 159 | + for _, tc := range tests { |
| 160 | + t.Run(tc.name, func(t *testing.T) { |
| 161 | + result := OptimizeItems(tc.items) |
| 162 | + assert.Equal(t, tc.expected, result) |
| 163 | + }) |
| 164 | + } |
| 165 | +} |
| 166 | + |
| 167 | +func TestOptimizeItems_SkipsFillRateBelowMinRows(t *testing.T) { |
| 168 | + items := []map[string]any{ |
| 169 | + {"title": "a", "rare": "x"}, |
| 170 | + {"title": "b"}, |
| 171 | + } |
| 172 | + |
| 173 | + result := OptimizeItems(items) |
| 174 | + assert.Equal(t, "x", result[0]["rare"]) |
| 175 | +} |
| 176 | + |
| 177 | +func TestPreservedFields(t *testing.T) { |
| 178 | + original := preservedFields |
| 179 | + defer func() { preservedFields = original }() |
| 180 | + |
| 181 | + preservedFields = map[string]bool{ |
| 182 | + "html_url": true, |
| 183 | + "clone_url": true, |
| 184 | + } |
| 185 | + |
| 186 | + result := optimizeItem(map[string]any{ |
| 187 | + "html_url": "https://github.com/repo/1", |
| 188 | + "clone_url": "https://github.com/repo/1.git", |
| 189 | + "avatar_url": "https://avatars.githubusercontent.com/1", |
| 190 | + "user.html_url": "https://github.com/user", |
| 191 | + "user.clone_url": "https://github.com/user.git", |
| 192 | + }) |
| 193 | + |
| 194 | + assert.Contains(t, result, "html_url") |
| 195 | + assert.Contains(t, result, "clone_url") |
| 196 | + assert.NotContains(t, result, "avatar_url") |
| 197 | + assert.NotContains(t, result, "user.html_url") |
| 198 | + assert.NotContains(t, result, "user.clone_url") |
| 199 | +} |
| 200 | + |
| 201 | +func TestPreservedFields_ProtectsZeroValues(t *testing.T) { |
| 202 | + original := preservedFields |
| 203 | + defer func() { preservedFields = original }() |
| 204 | + |
| 205 | + preservedFields = map[string]bool{"draft": true} |
| 206 | + |
| 207 | + result := optimizeItem(map[string]any{ |
| 208 | + "draft": false, |
| 209 | + "body": "", |
| 210 | + }) |
| 211 | + |
| 212 | + assert.Contains(t, result, "draft") |
| 213 | + assert.NotContains(t, result, "body") |
| 214 | +} |
| 215 | + |
| 216 | +func TestPreservedFields_ProtectsFromCollectionSummarization(t *testing.T) { |
| 217 | + original := preservedFields |
| 218 | + defer func() { preservedFields = original }() |
| 219 | + |
| 220 | + preservedFields = map[string]bool{"assignees": true} |
| 221 | + |
| 222 | + assignees := []any{ |
| 223 | + map[string]any{"login": "alice", "id": float64(1)}, |
| 224 | + map[string]any{"login": "bob", "id": float64(2)}, |
| 225 | + } |
| 226 | + |
| 227 | + result := optimizeItem(map[string]any{ |
| 228 | + "assignees": assignees, |
| 229 | + "comments": []any{map[string]any{"id": "1"}, map[string]any{"id": "2"}}, |
| 230 | + }) |
| 231 | + |
| 232 | + assert.Equal(t, assignees, result["assignees"]) |
| 233 | + assert.Equal(t, "[2 items]", result["comments"]) |
| 234 | +} |
| 235 | + |
| 236 | +func TestCollectionFieldExtractors_SurviveFillRate(t *testing.T) { |
| 237 | + original := collectionFieldExtractors |
| 238 | + defer func() { collectionFieldExtractors = original }() |
| 239 | + |
| 240 | + collectionFieldExtractors = map[string][]string{"labels": {"name"}} |
| 241 | + |
| 242 | + items := []map[string]any{ |
| 243 | + {"title": "PR 1", "labels": "bug"}, |
| 244 | + {"title": "PR 2"}, |
| 245 | + {"title": "PR 3"}, |
| 246 | + {"title": "PR 4"}, |
| 247 | + } |
| 248 | + |
| 249 | + result := filterByFillRate(items, defaultFillRateThreshold) |
| 250 | + |
| 251 | + assert.Contains(t, result[0], "labels") |
| 252 | +} |
| 253 | + |
| 254 | +func TestMarshalItems_PlainSlice(t *testing.T) { |
| 255 | + type commit struct { |
| 256 | + SHA string `json:"sha"` |
| 257 | + Message string `json:"message"` |
| 258 | + URL string `json:"url"` |
| 259 | + } |
| 260 | + |
| 261 | + data := []commit{ |
| 262 | + {SHA: "abc123", Message: "fix bug", URL: "https://api.github.com/commits/abc123"}, |
| 263 | + {SHA: "def456", Message: "add feature", URL: "https://api.github.com/commits/def456"}, |
| 264 | + {SHA: "ghi789", Message: "update docs", URL: "https://api.github.com/commits/ghi789"}, |
| 265 | + } |
| 266 | + |
| 267 | + raw, err := MarshalItems(data) |
| 268 | + require.NoError(t, err) |
| 269 | + |
| 270 | + var result []map[string]any |
| 271 | + err = json.Unmarshal(raw, &result) |
| 272 | + require.NoError(t, err) |
| 273 | + |
| 274 | + for _, item := range result { |
| 275 | + assert.NotEmpty(t, item["sha"]) |
| 276 | + assert.NotEmpty(t, item["message"]) |
| 277 | + assert.Nil(t, item["url"]) |
| 278 | + } |
| 279 | +} |
| 280 | + |
| 281 | +func TestMarshalItems_WrappedObject(t *testing.T) { |
| 282 | + data := map[string]any{ |
| 283 | + "issues": []any{ |
| 284 | + map[string]any{ |
| 285 | + "title": "bug report", |
| 286 | + "url": "https://api.github.com/issues/1", |
| 287 | + "html_url": "https://github.com/issues/1", |
| 288 | + "draft": false, |
| 289 | + }, |
| 290 | + map[string]any{ |
| 291 | + "title": "feature request", |
| 292 | + "url": "https://api.github.com/issues/2", |
| 293 | + "html_url": "https://github.com/issues/2", |
| 294 | + "draft": false, |
| 295 | + }, |
| 296 | + map[string]any{ |
| 297 | + "title": "docs update", |
| 298 | + "url": "https://api.github.com/issues/3", |
| 299 | + "html_url": "https://github.com/issues/3", |
| 300 | + "draft": false, |
| 301 | + }, |
| 302 | + }, |
| 303 | + "totalCount": 100, |
| 304 | + "pageInfo": map[string]any{ |
| 305 | + "hasNextPage": true, |
| 306 | + "endCursor": "abc123", |
| 307 | + }, |
| 308 | + } |
| 309 | + |
| 310 | + raw, err := MarshalItems(data) |
| 311 | + require.NoError(t, err) |
| 312 | + |
| 313 | + var result map[string]any |
| 314 | + err = json.Unmarshal(raw, &result) |
| 315 | + require.NoError(t, err) |
| 316 | + |
| 317 | + assert.NotNil(t, result["totalCount"]) |
| 318 | + assert.NotNil(t, result["pageInfo"]) |
| 319 | + |
| 320 | + issues, ok := result["issues"].([]any) |
| 321 | + require.True(t, ok) |
| 322 | + require.Len(t, issues, 3) |
| 323 | + |
| 324 | + for _, issue := range issues { |
| 325 | + m := issue.(map[string]any) |
| 326 | + assert.NotEmpty(t, m["title"]) |
| 327 | + assert.NotEmpty(t, m["html_url"]) |
| 328 | + assert.Nil(t, m["url"]) |
| 329 | + assert.Nil(t, m["draft"]) |
| 330 | + } |
| 331 | +} |
0 commit comments