github.com/exercism/v2-configlet@v3.9.2+incompatible/cmd/lint_test.go (about) 1 package cmd 2 3 import ( 4 "fmt" 5 "io/ioutil" 6 "net/http" 7 "net/http/httptest" 8 "path/filepath" 9 "sort" 10 "testing" 11 12 "github.com/exercism/configlet/track" 13 "github.com/exercism/configlet/ui" 14 "github.com/stretchr/testify/assert" 15 ) 16 17 var ( 18 // The JSON needs to have nulls, not empty strings. 19 // We cannot take the address of strings. 20 apple = "apple" 21 banana = "banana" 22 unknown = "unknown" 23 ) 24 25 func TestLintTrack(t *testing.T) { 26 originalNoHTTP := noHTTP 27 noHTTP = true 28 defer func() { 29 noHTTP = originalNoHTTP 30 }() 31 32 originalOut := ui.Out 33 originalErrOut := ui.ErrOut 34 ui.Out = ioutil.Discard 35 ui.ErrOut = ioutil.Discard 36 defer func() { 37 ui.Out = originalOut 38 ui.ErrOut = originalErrOut 39 }() 40 41 lintTests := []struct { 42 desc string 43 path string 44 expected bool 45 }{ 46 { 47 desc: "should fail when given a track containing one or more lint failures.", 48 path: "../fixtures/numbers", 49 expected: true, 50 }, 51 { 52 desc: "should fail when given a track containing malformed configuration data.", 53 path: "../fixtures/broken-maintainers", 54 expected: true, 55 }, 56 { 57 desc: "should fail when given a track missing READMEs.", 58 path: "../fixtures/missing-readme", 59 expected: true, 60 }, 61 { 62 desc: "should not fail when given a track with all of its bits in place.", 63 path: "../fixtures/lint/valid-track", 64 expected: false, 65 }, 66 } 67 68 for _, tt := range lintTests { 69 failed := lintTrack(filepath.FromSlash(tt.path)) 70 assert.Equal(t, tt.expected, failed, tt.desc) 71 } 72 } 73 74 func TestMissingImplementations(t *testing.T) { 75 track := track.Track{ 76 Config: track.Config{ 77 Exercises: []track.ExerciseMetadata{ 78 {Slug: "apple"}, 79 {Slug: "banana"}, 80 {Slug: "cherry"}, 81 }, 82 }, 83 Exercises: []track.Exercise{ 84 {Slug: "apple"}, 85 }, 86 } 87 88 slugs := missingImplementations(track) 89 90 if len(slugs) != 2 { 91 t.Fatalf("Expected missing implementations in 2 exercises, missing in %d", len(slugs)) 92 } 93 94 sort.Strings(slugs) 95 96 assert.Equal(t, "banana", slugs[0]) 97 assert.Equal(t, "cherry", slugs[1]) 98 } 99 100 func TestMissingMetadata(t *testing.T) { 101 track := track.Track{ 102 Config: track.Config{ 103 Exercises: []track.ExerciseMetadata{ 104 {Slug: "apple"}, 105 }, 106 }, 107 Exercises: []track.Exercise{ 108 {Slug: "apple"}, 109 {Slug: "banana"}, 110 {Slug: "cherry"}, 111 }, 112 } 113 114 slugs := missingMetadata(track) 115 116 if len(slugs) != 2 { 117 t.Fatalf("Expected missing metadata in 2 exercises, missing in %d", len(slugs)) 118 } 119 120 sort.Strings(slugs) 121 122 assert.Equal(t, "banana", slugs[0]) 123 assert.Equal(t, "cherry", slugs[1]) 124 } 125 126 func TestMissingReadme(t *testing.T) { 127 track := track.Track{ 128 Exercises: []track.Exercise{ 129 {Slug: "apple"}, 130 {Slug: "banana", ReadmePath: "README.md"}, 131 {Slug: "cherry"}, 132 }, 133 } 134 135 slugs := missingReadme(track) 136 137 if len(slugs) != 2 { 138 t.Fatalf("Expected missing READMEs in 2 exercises, missing in %d", len(slugs)) 139 } 140 141 sort.Strings(slugs) 142 143 assert.Equal(t, "apple", slugs[0]) 144 assert.Equal(t, "cherry", slugs[1]) 145 } 146 147 func TestMissingSolution(t *testing.T) { 148 track := track.Track{ 149 Exercises: []track.Exercise{ 150 {Slug: "apple"}, 151 {Slug: "banana", SolutionPath: "b.txt"}, 152 {Slug: "cherry"}, 153 }, 154 } 155 156 slugs := missingSolution(track) 157 158 if len(slugs) != 2 { 159 t.Fatalf("Expected missing solutions in 2 exercises, missing in %d", len(slugs)) 160 } 161 162 sort.Strings(slugs) 163 164 assert.Equal(t, "apple", slugs[0]) 165 assert.Equal(t, "cherry", slugs[1]) 166 } 167 168 func TestMissingTestSuite(t *testing.T) { 169 track := track.Track{ 170 Exercises: []track.Exercise{ 171 {Slug: "apple"}, 172 {Slug: "banana", TestSuitePath: "b_test.ext"}, 173 {Slug: "cherry"}, 174 }, 175 } 176 177 slugs := missingTestSuite(track) 178 179 if len(slugs) != 2 { 180 t.Fatalf("Expected missing test in 2 exercises, missing in %d", len(slugs)) 181 } 182 183 sort.Strings(slugs) 184 185 assert.Equal(t, "apple", slugs[0]) 186 assert.Equal(t, "cherry", slugs[1]) 187 } 188 189 func TestForegoneViolations(t *testing.T) { 190 track := track.Track{ 191 Config: track.Config{ 192 ForegoneSlugs: []string{"banana", "cherry"}, 193 }, 194 Exercises: []track.Exercise{ 195 {Slug: "apple"}, 196 {Slug: "banana"}, 197 {Slug: "cherry"}, 198 }, 199 } 200 201 slugs := foregoneViolations(track) 202 203 if len(slugs) != 2 { 204 t.Fatalf("Expected foregone violations in 2 exercises, violations in %d", len(slugs)) 205 } 206 207 sort.Strings(slugs) 208 209 assert.Equal(t, "banana", slugs[0]) 210 assert.Equal(t, "cherry", slugs[1]) 211 } 212 213 func TestDuplicateSlugs(t *testing.T) { 214 track := track.Track{ 215 Config: track.Config{ 216 Exercises: []track.ExerciseMetadata{ 217 {Slug: "apple"}, 218 {Slug: "banana"}, 219 {Slug: "cherry"}, 220 }, 221 DeprecatedSlugs: []string{"apple"}, 222 ForegoneSlugs: []string{"banana"}, 223 }, 224 } 225 226 slugs := duplicateSlugs(track) 227 228 if len(slugs) != 2 { 229 t.Fatalf("Expected 2 duplicate slugs, found %d", len(slugs)) 230 } 231 232 sort.Strings(slugs) 233 234 assert.Equal(t, "apple", slugs[0]) 235 assert.Equal(t, "banana", slugs[1]) 236 } 237 238 func TestDuplicateUUID(t *testing.T) { 239 uuidTests := []struct { 240 desc string 241 expected int 242 config track.Config 243 }{ 244 { 245 desc: "should not complain about a conflicting UUID for exercises with missing UUIDs.", 246 expected: 0, 247 config: track.Config{ 248 Exercises: []track.ExerciseMetadata{ 249 {Slug: "apple", UUID: ""}, 250 {Slug: "banana", UUID: ""}, 251 }, 252 }, 253 }, 254 { 255 desc: "should complain that multiple exercises have a conflicting UUID.", 256 expected: 1, 257 config: track.Config{ 258 Exercises: []track.ExerciseMetadata{ 259 {Slug: "cherry", UUID: "ccc"}, 260 {Slug: "diakon", UUID: "abc"}, 261 {Slug: "eggplant", UUID: "ccc"}, 262 }, 263 }, 264 }, 265 } 266 267 for _, tt := range uuidTests { 268 track := track.Track{Config: tt.config} 269 uuids := duplicateUUID(track) 270 271 assert.Equal(t, tt.expected, len(uuids), tt.desc) 272 } 273 } 274 275 func TestDuplicateTrackUUID(t *testing.T) { 276 fakeEndpoint := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 277 w.WriteHeader(http.StatusConflict) 278 fmt.Fprintln(w, `{"uuids": ["ccc"]}`) 279 }) 280 281 ts := httptest.NewServer(fakeEndpoint) 282 defer ts.Close() 283 284 originalUUIDValidationURL := UUIDValidationURL 285 UUIDValidationURL = ts.URL 286 defer func() { UUIDValidationURL = originalUUIDValidationURL }() 287 288 expected := []string{"ccc"} 289 track := track.Track{ 290 Config: track.Config{ 291 Exercises: []track.ExerciseMetadata{ 292 {Slug: "apple", UUID: "abc"}, 293 {Slug: "banana", UUID: expected[0]}, 294 }, 295 }, 296 } 297 298 uuids := duplicateTrackUUID(track) 299 assert.Equal(t, len(expected), len(uuids)) 300 assert.Equal(t, expected[0], uuids[0]) 301 302 } 303 304 func TestLockedCoreViolation(t *testing.T) { 305 trackTests := []struct { 306 desc string 307 expectedViolations int 308 expectedSlug string 309 config track.Config 310 }{ 311 { 312 desc: "should have one locked core violation", 313 expectedViolations: 1, 314 expectedSlug: "apple", 315 config: track.Config{ 316 Exercises: []track.ExerciseMetadata{ 317 { 318 Slug: "apple", 319 UnlockedBy: &banana, 320 IsCore: true, 321 }, 322 { 323 Slug: "banana", 324 UnlockedBy: nil, 325 IsCore: false, 326 }, 327 }, 328 }, 329 }, 330 { 331 desc: "should have no locked core violations", 332 expectedViolations: 0, 333 expectedSlug: "", 334 config: track.Config{ 335 Exercises: []track.ExerciseMetadata{ 336 { 337 Slug: "apple", 338 UnlockedBy: nil, 339 IsCore: true, 340 }, 341 { 342 Slug: "banana", 343 UnlockedBy: &apple, 344 IsCore: false, 345 }, 346 { 347 Slug: "cherry", 348 UnlockedBy: nil, 349 IsCore: false, 350 }, 351 }, 352 }, 353 }, 354 } 355 356 for _, tt := range trackTests { 357 track := track.Track{Config: tt.config} 358 slugs := lockedCoreViolation(track) 359 360 assert.Equal(t, tt.expectedViolations, len(slugs), tt.desc) 361 362 if len(slugs) > 0 { 363 assert.Equal(t, tt.expectedSlug, slugs[0], tt.desc) 364 } 365 } 366 } 367 368 func TestUnlockedByViolations(t *testing.T) { 369 trackTests := []struct { 370 desc string 371 expectedViolations int 372 expectedSlug string 373 config track.Config 374 }{ 375 { 376 desc: "should have one unlocked by violation from non-core exercise", 377 expectedViolations: 1, 378 expectedSlug: "banana", 379 config: track.Config{ 380 Exercises: []track.ExerciseMetadata{ 381 { 382 Slug: "apple", 383 UnlockedBy: nil, 384 IsCore: false, 385 }, 386 { 387 Slug: "banana", 388 UnlockedBy: &apple, 389 IsCore: false, 390 }, 391 }, 392 }, 393 }, 394 { 395 desc: "should have one unlocked by violation from non-existent exercise", 396 expectedViolations: 1, 397 expectedSlug: "cherry", 398 config: track.Config{ 399 Exercises: []track.ExerciseMetadata{ 400 { 401 Slug: "apple", 402 UnlockedBy: nil, 403 IsCore: true, 404 }, 405 { 406 Slug: "banana", 407 UnlockedBy: &apple, 408 IsCore: false, 409 }, 410 { 411 Slug: "cherry", 412 UnlockedBy: &unknown, 413 IsCore: false, 414 }, 415 }, 416 }, 417 }, 418 { 419 desc: "should have no unlocked by violations", 420 expectedViolations: 0, 421 expectedSlug: "", 422 config: track.Config{ 423 Exercises: []track.ExerciseMetadata{ 424 { 425 Slug: "apple", 426 UnlockedBy: nil, 427 IsCore: true, 428 }, 429 { 430 Slug: "banana", 431 UnlockedBy: &apple, 432 IsCore: false, 433 }, 434 }, 435 }, 436 }, 437 } 438 439 for _, tt := range trackTests { 440 track := track.Track{Config: tt.config} 441 slugs := unlockedByValidExercise(track) 442 443 assert.Equal(t, tt.expectedViolations, len(slugs), tt.desc) 444 445 if len(slugs) > 0 { 446 assert.Equal(t, tt.expectedSlug, slugs[0], tt.desc) 447 } 448 } 449 }