test: run unit tests in parallel and enable the tparallel linter
t.Parallel in domain, app, clientlimit, web, githttp, rpc and eventbus unit tests; tests that use t.Setenv, shared rigs or package-level state stay serial.
- commit
- 895432914bb6b3475e68b8ac11d804456b314227
- author
- brice · · 2026-10-11 20:08 UTC
- parent
- 0e9351b
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
46 files · +434 -1
M .golangci.yml +1 -0
| @@ -7,6 +7,7 @@ linters: | |||
| 7 | 7 | - ineffassign | |
| 8 | 8 | - misspell | |
| 9 | 9 | - staticcheck | |
| 10 | + | - tparallel | |
| 10 | 11 | - unconvert | |
| 11 | 12 | - unused | |
| 12 | 13 | exclusions: | |
| @@ -7,6 +7,7 @@ linters: | |||||
| 7 | - ineffassign | 7 | - ineffassign | ||
| 8 | - misspell | 8 | - misspell | ||
| 9 | - staticcheck | 9 | - staticcheck | ||
| 10 | + | - tparallel | |||
| 10 | - unconvert | 11 | - unconvert | ||
| 11 | - unused | 12 | - unused | ||
| 12 | exclusions: | 13 | exclusions: | ||
M internal/adapters/eventbus/bus_test.go +5 -0
| @@ -36,6 +36,7 @@ func recv(t *testing.T, ch <-chan domain.Event) domain.Event { | |||
| 36 | 36 | } | |
| 37 | 37 | ||
| 38 | 38 | func TestFanOut(t *testing.T) { | |
| 39 | + | t.Parallel() | |
| 39 | 40 | synctest.Test(t, func(t *testing.T) { | |
| 40 | 41 | ctx, cancel := context.WithCancel(context.Background()) | |
| 41 | 42 | defer cancel() | |
| @@ -52,6 +53,7 @@ func TestFanOut(t *testing.T) { | |||
| 52 | 53 | } | |
| 53 | 54 | ||
| 54 | 55 | func TestOnlyEventsFromSubscription(t *testing.T) { | |
| 56 | + | t.Parallel() | |
| 55 | 57 | synctest.Test(t, func(t *testing.T) { | |
| 56 | 58 | ctx, cancel := context.WithCancel(context.Background()) | |
| 57 | 59 | defer cancel() | |
| @@ -66,6 +68,7 @@ func TestOnlyEventsFromSubscription(t *testing.T) { | |||
| 66 | 68 | } | |
| 67 | 69 | ||
| 68 | 70 | func TestSlowSubscriberGetsGapAndDoesNotBlock(t *testing.T) { | |
| 71 | + | t.Parallel() | |
| 69 | 72 | synctest.Test(t, func(t *testing.T) { | |
| 70 | 73 | ctx, cancel := context.WithCancel(context.Background()) | |
| 71 | 74 | defer cancel() | |
| @@ -99,6 +102,7 @@ func TestSlowSubscriberGetsGapAndDoesNotBlock(t *testing.T) { | |||
| 99 | 102 | } | |
| 100 | 103 | ||
| 101 | 104 | func TestUnsubscribeOnContext(t *testing.T) { | |
| 105 | + | t.Parallel() | |
| 102 | 106 | synctest.Test(t, func(t *testing.T) { | |
| 103 | 107 | b := eventbus.New() | |
| 104 | 108 | ctx, cancel := context.WithCancel(context.Background()) | |
| @@ -120,6 +124,7 @@ func TestUnsubscribeOnContext(t *testing.T) { | |||
| 120 | 124 | } | |
| 121 | 125 | ||
| 122 | 126 | func TestConcurrentPublishSubscribe(t *testing.T) { | |
| 127 | + | t.Parallel() | |
| 123 | 128 | synctest.Test(t, func(t *testing.T) { | |
| 124 | 129 | b := eventbus.NewWithBuffer(8) | |
| 125 | 130 | ctx, cancel := context.WithCancel(context.Background()) | |
| @@ -36,6 +36,7 @@ func recv(t *testing.T, ch <-chan domain.Event) domain.Event { | |||||
| 36 | } | 36 | } | ||
| 37 | 37 | ||||
| 38 | func TestFanOut(t *testing.T) { | 38 | func TestFanOut(t *testing.T) { | ||
| 39 | + | t.Parallel() | |||
| 39 | synctest.Test(t, func(t *testing.T) { | 40 | synctest.Test(t, func(t *testing.T) { | ||
| 40 | ctx, cancel := context.WithCancel(context.Background()) | 41 | ctx, cancel := context.WithCancel(context.Background()) | ||
| 41 | defer cancel() | 42 | defer cancel() | ||
| @@ -52,6 +53,7 @@ func TestFanOut(t *testing.T) { | |||||
| 52 | } | 53 | } | ||
| 53 | 54 | ||||
| 54 | func TestOnlyEventsFromSubscription(t *testing.T) { | 55 | func TestOnlyEventsFromSubscription(t *testing.T) { | ||
| 56 | + | t.Parallel() | |||
| 55 | synctest.Test(t, func(t *testing.T) { | 57 | synctest.Test(t, func(t *testing.T) { | ||
| 56 | ctx, cancel := context.WithCancel(context.Background()) | 58 | ctx, cancel := context.WithCancel(context.Background()) | ||
| 57 | defer cancel() | 59 | defer cancel() | ||
| @@ -66,6 +68,7 @@ func TestOnlyEventsFromSubscription(t *testing.T) { | |||||
| 66 | } | 68 | } | ||
| 67 | 69 | ||||
| 68 | func TestSlowSubscriberGetsGapAndDoesNotBlock(t *testing.T) { | 70 | func TestSlowSubscriberGetsGapAndDoesNotBlock(t *testing.T) { | ||
| 71 | + | t.Parallel() | |||
| 69 | synctest.Test(t, func(t *testing.T) { | 72 | synctest.Test(t, func(t *testing.T) { | ||
| 70 | ctx, cancel := context.WithCancel(context.Background()) | 73 | ctx, cancel := context.WithCancel(context.Background()) | ||
| 71 | defer cancel() | 74 | defer cancel() | ||
| @@ -99,6 +102,7 @@ func TestSlowSubscriberGetsGapAndDoesNotBlock(t *testing.T) { | |||||
| 99 | } | 102 | } | ||
| 100 | 103 | ||||
| 101 | func TestUnsubscribeOnContext(t *testing.T) { | 104 | func TestUnsubscribeOnContext(t *testing.T) { | ||
| 105 | + | t.Parallel() | |||
| 102 | synctest.Test(t, func(t *testing.T) { | 106 | synctest.Test(t, func(t *testing.T) { | ||
| 103 | b := eventbus.New() | 107 | b := eventbus.New() | ||
| 104 | ctx, cancel := context.WithCancel(context.Background()) | 108 | ctx, cancel := context.WithCancel(context.Background()) | ||
| @@ -120,6 +124,7 @@ func TestUnsubscribeOnContext(t *testing.T) { | |||||
| 120 | } | 124 | } | ||
| 121 | 125 | ||||
| 122 | func TestConcurrentPublishSubscribe(t *testing.T) { | 126 | func TestConcurrentPublishSubscribe(t *testing.T) { | ||
| 127 | + | t.Parallel() | |||
| 123 | synctest.Test(t, func(t *testing.T) { | 128 | synctest.Test(t, func(t *testing.T) { | ||
| 124 | b := eventbus.NewWithBuffer(8) | 129 | b := eventbus.NewWithBuffer(8) | ||
| 125 | ctx, cancel := context.WithCancel(context.Background()) | 130 | ctx, cancel := context.WithCancel(context.Background()) | ||
M internal/adapters/githttp/handler_test.go +19 -1
| @@ -206,7 +206,7 @@ func envOf(body string) map[string]string { | |||
| 206 | 206 | } | |
| 207 | 207 | ||
| 208 | 208 | func TestHandlerRoutesAndStatuses(t *testing.T) { | |
| 209 | - | r := newRig(t, nil) | |
| 209 | + | t.Parallel() | |
| 210 | 210 | tests := []struct { | |
| 211 | 211 | name string | |
| 212 | 212 | method string | |
| @@ -235,6 +235,8 @@ func TestHandlerRoutesAndStatuses(t *testing.T) { | |||
| 235 | 235 | } | |
| 236 | 236 | for _, tt := range tests { | |
| 237 | 237 | t.Run(tt.name, func(t *testing.T) { | |
| 238 | + | t.Parallel() | |
| 239 | + | r := newRig(t, nil) // a rig per subtest: its mocks record without locking | |
| 238 | 240 | user := "" | |
| 239 | 241 | if tt.pass != "" { | |
| 240 | 242 | user = "whatever" // the username is ignored | |
| @@ -251,6 +253,7 @@ func TestHandlerRoutesAndStatuses(t *testing.T) { | |||
| 251 | 253 | } | |
| 252 | 254 | ||
| 253 | 255 | func TestHandlerPushAuthorizationAnswers(t *testing.T) { | |
| 256 | + | t.Parallel() | |
| 254 | 257 | r := newRig(t, nil) | |
| 255 | 258 | get := func() int { | |
| 256 | 259 | code, _, _ := r.do(t, "GET", "/p.git/info/refs?service=git-receive-pack", "u", "root-secret", nil, nil) | |
| @@ -282,6 +285,7 @@ type errBoom struct{} | |||
| 282 | 285 | func (errBoom) Error() string { return "boom with /internal/detail" } | |
| 283 | 286 | ||
| 284 | 287 | func TestHandlerBackendEnvironment(t *testing.T) { | |
| 288 | + | t.Parallel() | |
| 285 | 289 | r := newRig(t, nil) | |
| 286 | 290 | hdr := map[string]string{ | |
| 287 | 291 | "Cookie": "session=secret", | |
| @@ -338,12 +342,14 @@ func TestHandlerBackendEnvironment(t *testing.T) { | |||
| 338 | 342 | } | |
| 339 | 343 | ||
| 340 | 344 | func TestHandlerNormalizesRepositoryPath(t *testing.T) { | |
| 345 | + | t.Parallel() | |
| 341 | 346 | r := newRig(t, nil) | |
| 342 | 347 | _, body, _ := r.do(t, "GET", "/pub/info/refs?service=git-upload-pack", "", "", nil, nil) | |
| 343 | 348 | assert.Equal(t, "/pub.git/info/refs", envOf(body)["PATH_INFO"], "the backend always sees <name>.git") | |
| 344 | 349 | } | |
| 345 | 350 | ||
| 346 | 351 | func TestHandlerChunkedBodyIsStreamed(t *testing.T) { | |
| 352 | + | t.Parallel() | |
| 347 | 353 | r := newRig(t, nil) | |
| 348 | 354 | payload := strings.Repeat("x", 300_000) | |
| 349 | 355 | // An io.Reader of unknown length makes the client send chunked. | |
| @@ -359,6 +365,7 @@ func TestHandlerChunkedBodyIsStreamed(t *testing.T) { | |||
| 359 | 365 | } | |
| 360 | 366 | ||
| 361 | 367 | func TestHandlerBodyLimit(t *testing.T) { | |
| 368 | + | t.Parallel() | |
| 362 | 369 | r := newRig(t, func(c *githttp.Config) { c.MaxBodyBytes = 1000 }) | |
| 363 | 370 | big := strings.Repeat("x", 5000) | |
| 364 | 371 | code, _, _ := r.do(t, "POST", "/pub.git/git-receive-pack", "u", "root-secret", strings.NewReader(big), nil) | |
| @@ -376,6 +383,7 @@ func TestHandlerBodyLimit(t *testing.T) { | |||
| 376 | 383 | } | |
| 377 | 384 | ||
| 378 | 385 | func TestHandlerUploadPackBodyIsBoundedBelowThePushLimit(t *testing.T) { | |
| 386 | + | t.Parallel() | |
| 379 | 387 | r := newRig(t, nil) // the push limit is 4 GiB | |
| 380 | 388 | big := strings.NewReader(strings.Repeat("x", 64<<20+1)) | |
| 381 | 389 | code, _, _ := r.do(t, "POST", "/pub.git/git-upload-pack", "", "", big, nil) | |
| @@ -383,6 +391,7 @@ func TestHandlerUploadPackBodyIsBoundedBelowThePushLimit(t *testing.T) { | |||
| 383 | 391 | } | |
| 384 | 392 | ||
| 385 | 393 | func TestHandlerServiceQuery(t *testing.T) { | |
| 394 | + | t.Parallel() | |
| 386 | 395 | r := newRig(t, nil) | |
| 387 | 396 | for _, q := range []string{ | |
| 388 | 397 | "service=git-upload-pack&service=git-receive-pack", | |
| @@ -400,6 +409,7 @@ func TestHandlerServiceQuery(t *testing.T) { | |||
| 400 | 409 | } | |
| 401 | 410 | ||
| 402 | 411 | func TestHandlerGitProtocolHeader(t *testing.T) { | |
| 412 | + | t.Parallel() | |
| 403 | 413 | r := newRig(t, nil) | |
| 404 | 414 | tests := []struct { | |
| 405 | 415 | value string | |
| @@ -437,6 +447,7 @@ func TestHandlerDoesNotInheritTheProcessEnvironment(t *testing.T) { | |||
| 437 | 447 | } | |
| 438 | 448 | ||
| 439 | 449 | func TestHandlerBackendFailure(t *testing.T) { | |
| 450 | + | t.Parallel() | |
| 440 | 451 | r := newRig(t, nil) | |
| 441 | 452 | r.backendErr = io.ErrUnexpectedEOF | |
| 442 | 453 | code, body, _ := r.do(t, "GET", "/pub.git/info/refs?service=git-upload-pack", "", "", nil, nil) | |
| @@ -445,6 +456,7 @@ func TestHandlerBackendFailure(t *testing.T) { | |||
| 445 | 456 | } | |
| 446 | 457 | ||
| 447 | 458 | func TestHandlerLimiter(t *testing.T) { | |
| 459 | + | t.Parallel() | |
| 448 | 460 | r := newRig(t, func(c *githttp.Config) { c.Limiter = clientlimit.NewLimiter(nil) }) | |
| 449 | 461 | for i := range clientlimit.MaxFailures { | |
| 450 | 462 | code, _, _ := r.do(t, "GET", "/pub.git/info/refs?service=git-upload-pack", "u", "wrong", nil, nil) | |
| @@ -460,6 +472,7 @@ func TestHandlerLimiter(t *testing.T) { | |||
| 460 | 472 | } | |
| 461 | 473 | ||
| 462 | 474 | func TestHandlerLimiterTrustedProxyViaConfig(t *testing.T) { | |
| 475 | + | t.Parallel() | |
| 463 | 476 | r := newRig(t, func(c *githttp.Config) { | |
| 464 | 477 | c.TrustedProxies = []netip.Prefix{netip.MustParsePrefix("127.0.0.0/8")} | |
| 465 | 478 | }) | |
| @@ -473,6 +486,7 @@ func TestHandlerLimiterTrustedProxyViaConfig(t *testing.T) { | |||
| 473 | 486 | } | |
| 474 | 487 | ||
| 475 | 488 | func TestHandlerBearerAndMalformedAuthorization(t *testing.T) { | |
| 489 | + | t.Parallel() | |
| 476 | 490 | r := newRig(t, nil) | |
| 477 | 491 | path := "/priv.git/info/refs?service=git-upload-pack" | |
| 478 | 492 | code, _, _ := r.do(t, "GET", path, "", "", nil, map[string]string{"Authorization": "Bearer reader-secret"}) | |
| @@ -493,8 +507,10 @@ func TestHandlerBearerAndMalformedAuthorization(t *testing.T) { | |||
| 493 | 507 | } | |
| 494 | 508 | ||
| 495 | 509 | func TestHandlerUsesTheFailureLimiter(t *testing.T) { | |
| 510 | + | t.Parallel() | |
| 496 | 511 | path := "/pub.git/info/refs?service=git-upload-pack" | |
| 497 | 512 | t.Run("a blocked client is refused before authentication", func(t *testing.T) { | |
| 513 | + | t.Parallel() | |
| 498 | 514 | limiter := ghmocks.NewFailureLimiter(t) | |
| 499 | 515 | limiter.EXPECT().Blocked(mock.Anything, "127.0.0.1").Return(true, 3*time.Second).Once() | |
| 500 | 516 | r := newRig(t, func(c *githttp.Config) { c.Limiter = limiter }) | |
| @@ -504,6 +520,7 @@ func TestHandlerUsesTheFailureLimiter(t *testing.T) { | |||
| 504 | 520 | assert.Zero(t, r.authenticateCalls()) | |
| 505 | 521 | }) | |
| 506 | 522 | t.Run("an invalid secret is recorded as a failure", func(t *testing.T) { | |
| 523 | + | t.Parallel() | |
| 507 | 524 | limiter := ghmocks.NewFailureLimiter(t) | |
| 508 | 525 | limiter.EXPECT().Blocked(mock.Anything, "127.0.0.1").Return(false, time.Duration(0)).Once() | |
| 509 | 526 | limiter.EXPECT().Fail(mock.Anything, "127.0.0.1").Once() | |
| @@ -512,6 +529,7 @@ func TestHandlerUsesTheFailureLimiter(t *testing.T) { | |||
| 512 | 529 | assert.Equal(t, 401, code) | |
| 513 | 530 | }) | |
| 514 | 531 | t.Run("a valid secret is not a failure", func(t *testing.T) { | |
| 532 | + | t.Parallel() | |
| 515 | 533 | limiter := ghmocks.NewFailureLimiter(t) | |
| 516 | 534 | limiter.EXPECT().Blocked(mock.Anything, "127.0.0.1").Return(false, time.Duration(0)).Once() | |
| 517 | 535 | r := newRig(t, func(c *githttp.Config) { c.Limiter = limiter }) | |
| @@ -206,7 +206,7 @@ func envOf(body string) map[string]string { | |||||
| 206 | } | 206 | } | ||
| 207 | 207 | ||||
| 208 | func TestHandlerRoutesAndStatuses(t *testing.T) { | 208 | func TestHandlerRoutesAndStatuses(t *testing.T) { | ||
| 209 | - | r := newRig(t, nil) | 209 | + | t.Parallel() |
| 210 | tests := []struct { | 210 | tests := []struct { | ||
| 211 | name string | 211 | name string | ||
| 212 | method string | 212 | method string | ||
| @@ -235,6 +235,8 @@ func TestHandlerRoutesAndStatuses(t *testing.T) { | |||||
| 235 | } | 235 | } | ||
| 236 | for _, tt := range tests { | 236 | for _, tt := range tests { | ||
| 237 | t.Run(tt.name, func(t *testing.T) { | 237 | t.Run(tt.name, func(t *testing.T) { | ||
| 238 | + | t.Parallel() | |||
| 239 | + | r := newRig(t, nil) // a rig per subtest: its mocks record without locking | |||
| 238 | user := "" | 240 | user := "" | ||
| 239 | if tt.pass != "" { | 241 | if tt.pass != "" { | ||
| 240 | user = "whatever" // the username is ignored | 242 | user = "whatever" // the username is ignored | ||
| @@ -251,6 +253,7 @@ func TestHandlerRoutesAndStatuses(t *testing.T) { | |||||
| 251 | } | 253 | } | ||
| 252 | 254 | ||||
| 253 | func TestHandlerPushAuthorizationAnswers(t *testing.T) { | 255 | func TestHandlerPushAuthorizationAnswers(t *testing.T) { | ||
| 256 | + | t.Parallel() | |||
| 254 | r := newRig(t, nil) | 257 | r := newRig(t, nil) | ||
| 255 | get := func() int { | 258 | get := func() int { | ||
| 256 | code, _, _ := r.do(t, "GET", "/p.git/info/refs?service=git-receive-pack", "u", "root-secret", nil, nil) | 259 | code, _, _ := r.do(t, "GET", "/p.git/info/refs?service=git-receive-pack", "u", "root-secret", nil, nil) | ||
| @@ -282,6 +285,7 @@ type errBoom struct{} | |||||
| 282 | func (errBoom) Error() string { return "boom with /internal/detail" } | 285 | func (errBoom) Error() string { return "boom with /internal/detail" } | ||
| 283 | 286 | ||||
| 284 | func TestHandlerBackendEnvironment(t *testing.T) { | 287 | func TestHandlerBackendEnvironment(t *testing.T) { | ||
| 288 | + | t.Parallel() | |||
| 285 | r := newRig(t, nil) | 289 | r := newRig(t, nil) | ||
| 286 | hdr := map[string]string{ | 290 | hdr := map[string]string{ | ||
| 287 | "Cookie": "session=secret", | 291 | "Cookie": "session=secret", | ||
| @@ -338,12 +342,14 @@ func TestHandlerBackendEnvironment(t *testing.T) { | |||||
| 338 | } | 342 | } | ||
| 339 | 343 | ||||
| 340 | func TestHandlerNormalizesRepositoryPath(t *testing.T) { | 344 | func TestHandlerNormalizesRepositoryPath(t *testing.T) { | ||
| 345 | + | t.Parallel() | |||
| 341 | r := newRig(t, nil) | 346 | r := newRig(t, nil) | ||
| 342 | _, body, _ := r.do(t, "GET", "/pub/info/refs?service=git-upload-pack", "", "", nil, nil) | 347 | _, body, _ := r.do(t, "GET", "/pub/info/refs?service=git-upload-pack", "", "", nil, nil) | ||
| 343 | assert.Equal(t, "/pub.git/info/refs", envOf(body)["PATH_INFO"], "the backend always sees <name>.git") | 348 | assert.Equal(t, "/pub.git/info/refs", envOf(body)["PATH_INFO"], "the backend always sees <name>.git") | ||
| 344 | } | 349 | } | ||
| 345 | 350 | ||||
| 346 | func TestHandlerChunkedBodyIsStreamed(t *testing.T) { | 351 | func TestHandlerChunkedBodyIsStreamed(t *testing.T) { | ||
| 352 | + | t.Parallel() | |||
| 347 | r := newRig(t, nil) | 353 | r := newRig(t, nil) | ||
| 348 | payload := strings.Repeat("x", 300_000) | 354 | payload := strings.Repeat("x", 300_000) | ||
| 349 | // An io.Reader of unknown length makes the client send chunked. | 355 | // An io.Reader of unknown length makes the client send chunked. | ||
| @@ -359,6 +365,7 @@ func TestHandlerChunkedBodyIsStreamed(t *testing.T) { | |||||
| 359 | } | 365 | } | ||
| 360 | 366 | ||||
| 361 | func TestHandlerBodyLimit(t *testing.T) { | 367 | func TestHandlerBodyLimit(t *testing.T) { | ||
| 368 | + | t.Parallel() | |||
| 362 | r := newRig(t, func(c *githttp.Config) { c.MaxBodyBytes = 1000 }) | 369 | r := newRig(t, func(c *githttp.Config) { c.MaxBodyBytes = 1000 }) | ||
| 363 | big := strings.Repeat("x", 5000) | 370 | big := strings.Repeat("x", 5000) | ||
| 364 | code, _, _ := r.do(t, "POST", "/pub.git/git-receive-pack", "u", "root-secret", strings.NewReader(big), nil) | 371 | code, _, _ := r.do(t, "POST", "/pub.git/git-receive-pack", "u", "root-secret", strings.NewReader(big), nil) | ||
| @@ -376,6 +383,7 @@ func TestHandlerBodyLimit(t *testing.T) { | |||||
| 376 | } | 383 | } | ||
| 377 | 384 | ||||
| 378 | func TestHandlerUploadPackBodyIsBoundedBelowThePushLimit(t *testing.T) { | 385 | func TestHandlerUploadPackBodyIsBoundedBelowThePushLimit(t *testing.T) { | ||
| 386 | + | t.Parallel() | |||
| 379 | r := newRig(t, nil) // the push limit is 4 GiB | 387 | r := newRig(t, nil) // the push limit is 4 GiB | ||
| 380 | big := strings.NewReader(strings.Repeat("x", 64<<20+1)) | 388 | big := strings.NewReader(strings.Repeat("x", 64<<20+1)) | ||
| 381 | code, _, _ := r.do(t, "POST", "/pub.git/git-upload-pack", "", "", big, nil) | 389 | code, _, _ := r.do(t, "POST", "/pub.git/git-upload-pack", "", "", big, nil) | ||
| @@ -383,6 +391,7 @@ func TestHandlerUploadPackBodyIsBoundedBelowThePushLimit(t *testing.T) { | |||||
| 383 | } | 391 | } | ||
| 384 | 392 | ||||
| 385 | func TestHandlerServiceQuery(t *testing.T) { | 393 | func TestHandlerServiceQuery(t *testing.T) { | ||
| 394 | + | t.Parallel() | |||
| 386 | r := newRig(t, nil) | 395 | r := newRig(t, nil) | ||
| 387 | for _, q := range []string{ | 396 | for _, q := range []string{ | ||
| 388 | "service=git-upload-pack&service=git-receive-pack", | 397 | "service=git-upload-pack&service=git-receive-pack", | ||
| @@ -400,6 +409,7 @@ func TestHandlerServiceQuery(t *testing.T) { | |||||
| 400 | } | 409 | } | ||
| 401 | 410 | ||||
| 402 | func TestHandlerGitProtocolHeader(t *testing.T) { | 411 | func TestHandlerGitProtocolHeader(t *testing.T) { | ||
| 412 | + | t.Parallel() | |||
| 403 | r := newRig(t, nil) | 413 | r := newRig(t, nil) | ||
| 404 | tests := []struct { | 414 | tests := []struct { | ||
| 405 | value string | 415 | value string | ||
| @@ -437,6 +447,7 @@ func TestHandlerDoesNotInheritTheProcessEnvironment(t *testing.T) { | |||||
| 437 | } | 447 | } | ||
| 438 | 448 | ||||
| 439 | func TestHandlerBackendFailure(t *testing.T) { | 449 | func TestHandlerBackendFailure(t *testing.T) { | ||
| 450 | + | t.Parallel() | |||
| 440 | r := newRig(t, nil) | 451 | r := newRig(t, nil) | ||
| 441 | r.backendErr = io.ErrUnexpectedEOF | 452 | r.backendErr = io.ErrUnexpectedEOF | ||
| 442 | code, body, _ := r.do(t, "GET", "/pub.git/info/refs?service=git-upload-pack", "", "", nil, nil) | 453 | code, body, _ := r.do(t, "GET", "/pub.git/info/refs?service=git-upload-pack", "", "", nil, nil) | ||
| @@ -445,6 +456,7 @@ func TestHandlerBackendFailure(t *testing.T) { | |||||
| 445 | } | 456 | } | ||
| 446 | 457 | ||||
| 447 | func TestHandlerLimiter(t *testing.T) { | 458 | func TestHandlerLimiter(t *testing.T) { | ||
| 459 | + | t.Parallel() | |||
| 448 | r := newRig(t, func(c *githttp.Config) { c.Limiter = clientlimit.NewLimiter(nil) }) | 460 | r := newRig(t, func(c *githttp.Config) { c.Limiter = clientlimit.NewLimiter(nil) }) | ||
| 449 | for i := range clientlimit.MaxFailures { | 461 | for i := range clientlimit.MaxFailures { | ||
| 450 | code, _, _ := r.do(t, "GET", "/pub.git/info/refs?service=git-upload-pack", "u", "wrong", nil, nil) | 462 | code, _, _ := r.do(t, "GET", "/pub.git/info/refs?service=git-upload-pack", "u", "wrong", nil, nil) | ||
| @@ -460,6 +472,7 @@ func TestHandlerLimiter(t *testing.T) { | |||||
| 460 | } | 472 | } | ||
| 461 | 473 | ||||
| 462 | func TestHandlerLimiterTrustedProxyViaConfig(t *testing.T) { | 474 | func TestHandlerLimiterTrustedProxyViaConfig(t *testing.T) { | ||
| 475 | + | t.Parallel() | |||
| 463 | r := newRig(t, func(c *githttp.Config) { | 476 | r := newRig(t, func(c *githttp.Config) { | ||
| 464 | c.TrustedProxies = []netip.Prefix{netip.MustParsePrefix("127.0.0.0/8")} | 477 | c.TrustedProxies = []netip.Prefix{netip.MustParsePrefix("127.0.0.0/8")} | ||
| 465 | }) | 478 | }) | ||
| @@ -473,6 +486,7 @@ func TestHandlerLimiterTrustedProxyViaConfig(t *testing.T) { | |||||
| 473 | } | 486 | } | ||
| 474 | 487 | ||||
| 475 | func TestHandlerBearerAndMalformedAuthorization(t *testing.T) { | 488 | func TestHandlerBearerAndMalformedAuthorization(t *testing.T) { | ||
| 489 | + | t.Parallel() | |||
| 476 | r := newRig(t, nil) | 490 | r := newRig(t, nil) | ||
| 477 | path := "/priv.git/info/refs?service=git-upload-pack" | 491 | path := "/priv.git/info/refs?service=git-upload-pack" | ||
| 478 | code, _, _ := r.do(t, "GET", path, "", "", nil, map[string]string{"Authorization": "Bearer reader-secret"}) | 492 | code, _, _ := r.do(t, "GET", path, "", "", nil, map[string]string{"Authorization": "Bearer reader-secret"}) | ||
| @@ -493,8 +507,10 @@ func TestHandlerBearerAndMalformedAuthorization(t *testing.T) { | |||||
| 493 | } | 507 | } | ||
| 494 | 508 | ||||
| 495 | func TestHandlerUsesTheFailureLimiter(t *testing.T) { | 509 | func TestHandlerUsesTheFailureLimiter(t *testing.T) { | ||
| 510 | + | t.Parallel() | |||
| 496 | path := "/pub.git/info/refs?service=git-upload-pack" | 511 | path := "/pub.git/info/refs?service=git-upload-pack" | ||
| 497 | t.Run("a blocked client is refused before authentication", func(t *testing.T) { | 512 | t.Run("a blocked client is refused before authentication", func(t *testing.T) { | ||
| 513 | + | t.Parallel() | |||
| 498 | limiter := ghmocks.NewFailureLimiter(t) | 514 | limiter := ghmocks.NewFailureLimiter(t) | ||
| 499 | limiter.EXPECT().Blocked(mock.Anything, "127.0.0.1").Return(true, 3*time.Second).Once() | 515 | limiter.EXPECT().Blocked(mock.Anything, "127.0.0.1").Return(true, 3*time.Second).Once() | ||
| 500 | r := newRig(t, func(c *githttp.Config) { c.Limiter = limiter }) | 516 | r := newRig(t, func(c *githttp.Config) { c.Limiter = limiter }) | ||
| @@ -504,6 +520,7 @@ func TestHandlerUsesTheFailureLimiter(t *testing.T) { | |||||
| 504 | assert.Zero(t, r.authenticateCalls()) | 520 | assert.Zero(t, r.authenticateCalls()) | ||
| 505 | }) | 521 | }) | ||
| 506 | t.Run("an invalid secret is recorded as a failure", func(t *testing.T) { | 522 | t.Run("an invalid secret is recorded as a failure", func(t *testing.T) { | ||
| 523 | + | t.Parallel() | |||
| 507 | limiter := ghmocks.NewFailureLimiter(t) | 524 | limiter := ghmocks.NewFailureLimiter(t) | ||
| 508 | limiter.EXPECT().Blocked(mock.Anything, "127.0.0.1").Return(false, time.Duration(0)).Once() | 525 | limiter.EXPECT().Blocked(mock.Anything, "127.0.0.1").Return(false, time.Duration(0)).Once() | ||
| 509 | limiter.EXPECT().Fail(mock.Anything, "127.0.0.1").Once() | 526 | limiter.EXPECT().Fail(mock.Anything, "127.0.0.1").Once() | ||
| @@ -512,6 +529,7 @@ func TestHandlerUsesTheFailureLimiter(t *testing.T) { | |||||
| 512 | assert.Equal(t, 401, code) | 529 | assert.Equal(t, 401, code) | ||
| 513 | }) | 530 | }) | ||
| 514 | t.Run("a valid secret is not a failure", func(t *testing.T) { | 531 | t.Run("a valid secret is not a failure", func(t *testing.T) { | ||
| 532 | + | t.Parallel() | |||
| 515 | limiter := ghmocks.NewFailureLimiter(t) | 533 | limiter := ghmocks.NewFailureLimiter(t) | ||
| 516 | limiter.EXPECT().Blocked(mock.Anything, "127.0.0.1").Return(false, time.Duration(0)).Once() | 534 | limiter.EXPECT().Blocked(mock.Anything, "127.0.0.1").Return(false, time.Duration(0)).Once() | ||
| 517 | r := newRig(t, func(c *githttp.Config) { c.Limiter = limiter }) | 535 | r := newRig(t, func(c *githttp.Config) { c.Limiter = limiter }) | ||
M internal/adapters/githttp/hooks_test.go +7 -0
| @@ -79,6 +79,7 @@ func raw(t *testing.T, sock, line string) githttp.HookReply { | |||
| 79 | 79 | } | |
| 80 | 80 | ||
| 81 | 81 | func TestHookSocketMode(t *testing.T) { | |
| 82 | + | t.Parallel() | |
| 82 | 83 | _, sock := startHooks(t, ghmocks.NewPushHooks(t)) | |
| 83 | 84 | fi, err := os.Stat(sock) | |
| 84 | 85 | require.NoError(t, err) | |
| @@ -87,6 +88,7 @@ func TestHookSocketMode(t *testing.T) { | |||
| 87 | 88 | } | |
| 88 | 89 | ||
| 89 | 90 | func TestHookServerReplacesStaleSocketFile(t *testing.T) { | |
| 91 | + | t.Parallel() | |
| 90 | 92 | dir, err := os.MkdirTemp("", "hk") | |
| 91 | 93 | require.NoError(t, err) | |
| 92 | 94 | defer func() { _ = os.RemoveAll(dir) }() | |
| @@ -98,6 +100,7 @@ func TestHookServerReplacesStaleSocketFile(t *testing.T) { | |||
| 98 | 100 | } | |
| 99 | 101 | ||
| 100 | 102 | func TestHookServerRejectsTooLongSocketPath(t *testing.T) { | |
| 103 | + | t.Parallel() | |
| 101 | 104 | s := githttp.NewHookServer("/"+strings.Repeat("a", 120)+"/hook.sock", ghmocks.NewPushHooks(t)) | |
| 102 | 105 | err := s.Listen(context.Background()) | |
| 103 | 106 | require.Error(t, err) | |
| @@ -198,6 +201,7 @@ func TestHookProtocol(t *testing.T) { | |||
| 198 | 201 | // A shutdown while a push is being recorded must not drop the recording: the | |
| 199 | 202 | // push is already in the repository. | |
| 200 | 203 | func TestHookRecordSurvivesShutdown(t *testing.T) { | |
| 204 | + | t.Parallel() | |
| 201 | 205 | hooks := ghmocks.NewPushHooks(t) | |
| 202 | 206 | started, proceed := make(chan struct{}), make(chan struct{}) | |
| 203 | 207 | var cancelled, hasDeadline bool | |
| @@ -252,6 +256,7 @@ func TestHookRecordSurvivesShutdown(t *testing.T) { | |||
| 252 | 256 | } | |
| 253 | 257 | ||
| 254 | 258 | func TestHookConcurrentTokens(t *testing.T) { | |
| 259 | + | t.Parallel() | |
| 255 | 260 | hooks := ghmocks.NewPushHooks(t) | |
| 256 | 261 | srv, sock := startHooks(t, hooks) | |
| 257 | 262 | var wg sync.WaitGroup | |
| @@ -358,6 +363,7 @@ func TestRunHook(t *testing.T) { | |||
| 358 | 363 | } | |
| 359 | 364 | ||
| 360 | 365 | func TestWriteHookScripts(t *testing.T) { | |
| 366 | + | t.Parallel() | |
| 361 | 367 | dir := filepath.Join(t.TempDir(), "run", "hooks") | |
| 362 | 368 | exe := "/opt/it's here/\"ermite\" $HOME `x`/ermite" | |
| 363 | 369 | require.NoError(t, githttp.WriteHookScripts(dir, exe)) | |
| @@ -384,6 +390,7 @@ func TestWriteHookScripts(t *testing.T) { | |||
| 384 | 390 | ||
| 385 | 391 | // The shell really passes the awkward path and the arguments through. | |
| 386 | 392 | func TestHookScriptExecsTheBinaryWithQuotedPath(t *testing.T) { | |
| 393 | + | t.Parallel() | |
| 387 | 394 | base := filepath.Join(t.TempDir(), "it's a \"dir\" $X `y` ; z") | |
| 388 | 395 | require.NoError(t, os.MkdirAll(base, 0o755)) | |
| 389 | 396 | exe := filepath.Join(base, "fake ermite") | |
| @@ -79,6 +79,7 @@ func raw(t *testing.T, sock, line string) githttp.HookReply { | |||||
| 79 | } | 79 | } | ||
| 80 | 80 | ||||
| 81 | func TestHookSocketMode(t *testing.T) { | 81 | func TestHookSocketMode(t *testing.T) { | ||
| 82 | + | t.Parallel() | |||
| 82 | _, sock := startHooks(t, ghmocks.NewPushHooks(t)) | 83 | _, sock := startHooks(t, ghmocks.NewPushHooks(t)) | ||
| 83 | fi, err := os.Stat(sock) | 84 | fi, err := os.Stat(sock) | ||
| 84 | require.NoError(t, err) | 85 | require.NoError(t, err) | ||
| @@ -87,6 +88,7 @@ func TestHookSocketMode(t *testing.T) { | |||||
| 87 | } | 88 | } | ||
| 88 | 89 | ||||
| 89 | func TestHookServerReplacesStaleSocketFile(t *testing.T) { | 90 | func TestHookServerReplacesStaleSocketFile(t *testing.T) { | ||
| 91 | + | t.Parallel() | |||
| 90 | dir, err := os.MkdirTemp("", "hk") | 92 | dir, err := os.MkdirTemp("", "hk") | ||
| 91 | require.NoError(t, err) | 93 | require.NoError(t, err) | ||
| 92 | defer func() { _ = os.RemoveAll(dir) }() | 94 | defer func() { _ = os.RemoveAll(dir) }() | ||
| @@ -98,6 +100,7 @@ func TestHookServerReplacesStaleSocketFile(t *testing.T) { | |||||
| 98 | } | 100 | } | ||
| 99 | 101 | ||||
| 100 | func TestHookServerRejectsTooLongSocketPath(t *testing.T) { | 102 | func TestHookServerRejectsTooLongSocketPath(t *testing.T) { | ||
| 103 | + | t.Parallel() | |||
| 101 | s := githttp.NewHookServer("/"+strings.Repeat("a", 120)+"/hook.sock", ghmocks.NewPushHooks(t)) | 104 | s := githttp.NewHookServer("/"+strings.Repeat("a", 120)+"/hook.sock", ghmocks.NewPushHooks(t)) | ||
| 102 | err := s.Listen(context.Background()) | 105 | err := s.Listen(context.Background()) | ||
| 103 | require.Error(t, err) | 106 | require.Error(t, err) | ||
| @@ -198,6 +201,7 @@ func TestHookProtocol(t *testing.T) { | |||||
| 198 | // A shutdown while a push is being recorded must not drop the recording: the | 201 | // A shutdown while a push is being recorded must not drop the recording: the | ||
| 199 | // push is already in the repository. | 202 | // push is already in the repository. | ||
| 200 | func TestHookRecordSurvivesShutdown(t *testing.T) { | 203 | func TestHookRecordSurvivesShutdown(t *testing.T) { | ||
| 204 | + | t.Parallel() | |||
| 201 | hooks := ghmocks.NewPushHooks(t) | 205 | hooks := ghmocks.NewPushHooks(t) | ||
| 202 | started, proceed := make(chan struct{}), make(chan struct{}) | 206 | started, proceed := make(chan struct{}), make(chan struct{}) | ||
| 203 | var cancelled, hasDeadline bool | 207 | var cancelled, hasDeadline bool | ||
| @@ -252,6 +256,7 @@ func TestHookRecordSurvivesShutdown(t *testing.T) { | |||||
| 252 | } | 256 | } | ||
| 253 | 257 | ||||
| 254 | func TestHookConcurrentTokens(t *testing.T) { | 258 | func TestHookConcurrentTokens(t *testing.T) { | ||
| 259 | + | t.Parallel() | |||
| 255 | hooks := ghmocks.NewPushHooks(t) | 260 | hooks := ghmocks.NewPushHooks(t) | ||
| 256 | srv, sock := startHooks(t, hooks) | 261 | srv, sock := startHooks(t, hooks) | ||
| 257 | var wg sync.WaitGroup | 262 | var wg sync.WaitGroup | ||
| @@ -358,6 +363,7 @@ func TestRunHook(t *testing.T) { | |||||
| 358 | } | 363 | } | ||
| 359 | 364 | ||||
| 360 | func TestWriteHookScripts(t *testing.T) { | 365 | func TestWriteHookScripts(t *testing.T) { | ||
| 366 | + | t.Parallel() | |||
| 361 | dir := filepath.Join(t.TempDir(), "run", "hooks") | 367 | dir := filepath.Join(t.TempDir(), "run", "hooks") | ||
| 362 | exe := "/opt/it's here/\"ermite\" $HOME `x`/ermite" | 368 | exe := "/opt/it's here/\"ermite\" $HOME `x`/ermite" | ||
| 363 | require.NoError(t, githttp.WriteHookScripts(dir, exe)) | 369 | require.NoError(t, githttp.WriteHookScripts(dir, exe)) | ||
| @@ -384,6 +390,7 @@ func TestWriteHookScripts(t *testing.T) { | |||||
| 384 | 390 | ||||
| 385 | // The shell really passes the awkward path and the arguments through. | 391 | // The shell really passes the awkward path and the arguments through. | ||
| 386 | func TestHookScriptExecsTheBinaryWithQuotedPath(t *testing.T) { | 392 | func TestHookScriptExecsTheBinaryWithQuotedPath(t *testing.T) { | ||
| 393 | + | t.Parallel() | |||
| 387 | base := filepath.Join(t.TempDir(), "it's a \"dir\" $X `y` ; z") | 394 | base := filepath.Join(t.TempDir(), "it's a \"dir\" $X `y` ; z") | ||
| 388 | require.NoError(t, os.MkdirAll(base, 0o755)) | 395 | require.NoError(t, os.MkdirAll(base, 0o755)) | ||
| 389 | exe := filepath.Join(base, "fake ermite") | 396 | exe := filepath.Join(base, "fake ermite") | ||
M internal/adapters/githttp/limits_test.go +6 -0
| @@ -114,6 +114,7 @@ func (g *gatedRig) open() { | |||
| 114 | 114 | } | |
| 115 | 115 | ||
| 116 | 116 | func TestHandlerBackendLimits(t *testing.T) { | |
| 117 | + | t.Parallel() | |
| 117 | 118 | tests := []struct { | |
| 118 | 119 | name string | |
| 119 | 120 | cfg func(*githttp.Config) | |
| @@ -187,6 +188,7 @@ func TestHandlerBackendLimits(t *testing.T) { | |||
| 187 | 188 | } | |
| 188 | 189 | for _, tt := range tests { | |
| 189 | 190 | t.Run(tt.name, func(t *testing.T) { | |
| 191 | + | t.Parallel() | |
| 190 | 192 | g := newGatedRig(t, func(c *githttp.Config) { | |
| 191 | 193 | c.MaxUploadPacks, c.MaxReceivePacks, c.MaxPerClient = 8, 8, 8 | |
| 192 | 194 | tt.cfg(c) | |
| @@ -210,6 +212,7 @@ func TestHandlerBackendLimits(t *testing.T) { | |||
| 210 | 212 | } | |
| 211 | 213 | ||
| 212 | 214 | func TestHandlerBackendSlotsAreReleased(t *testing.T) { | |
| 215 | + | t.Parallel() | |
| 213 | 216 | g := newGatedRig(t, func(c *githttp.Config) { | |
| 214 | 217 | c.MaxUploadPacks, c.MaxReceivePacks, c.MaxPerClient = 1, 1, 1 | |
| 215 | 218 | }) | |
| @@ -230,6 +233,7 @@ func TestHandlerBackendSlotsAreReleased(t *testing.T) { | |||
| 230 | 233 | } | |
| 231 | 234 | ||
| 232 | 235 | func TestHandlerBackendFailureReleasesSlots(t *testing.T) { | |
| 236 | + | t.Parallel() | |
| 233 | 237 | r := newRig(t, func(c *githttp.Config) { c.MaxUploadPacks, c.MaxPerClient = 1, 1 }) | |
| 234 | 238 | r.backendErr = context.DeadlineExceeded | |
| 235 | 239 | for range 3 { | |
| @@ -251,6 +255,7 @@ func (d *deadlineRecorder) SetReadDeadline(t time.Time) error { | |||
| 251 | 255 | } | |
| 252 | 256 | ||
| 253 | 257 | func TestHandlerBodyReadDeadline(t *testing.T) { | |
| 258 | + | t.Parallel() | |
| 254 | 259 | tests := []struct { | |
| 255 | 260 | name string | |
| 256 | 261 | path string | |
| @@ -262,6 +267,7 @@ func TestHandlerBodyReadDeadline(t *testing.T) { | |||
| 262 | 267 | } | |
| 263 | 268 | for _, tt := range tests { | |
| 264 | 269 | t.Run(tt.name, func(t *testing.T) { | |
| 270 | + | t.Parallel() | |
| 265 | 271 | r := newRig(t, nil) | |
| 266 | 272 | req := httptest.NewRequest(http.MethodPost, tt.path, stringsReader("payload")) | |
| 267 | 273 | req.SetBasicAuth("u", "root-secret") | |
| @@ -114,6 +114,7 @@ func (g *gatedRig) open() { | |||||
| 114 | } | 114 | } | ||
| 115 | 115 | ||||
| 116 | func TestHandlerBackendLimits(t *testing.T) { | 116 | func TestHandlerBackendLimits(t *testing.T) { | ||
| 117 | + | t.Parallel() | |||
| 117 | tests := []struct { | 118 | tests := []struct { | ||
| 118 | name string | 119 | name string | ||
| 119 | cfg func(*githttp.Config) | 120 | cfg func(*githttp.Config) | ||
| @@ -187,6 +188,7 @@ func TestHandlerBackendLimits(t *testing.T) { | |||||
| 187 | } | 188 | } | ||
| 188 | for _, tt := range tests { | 189 | for _, tt := range tests { | ||
| 189 | t.Run(tt.name, func(t *testing.T) { | 190 | t.Run(tt.name, func(t *testing.T) { | ||
| 191 | + | t.Parallel() | |||
| 190 | g := newGatedRig(t, func(c *githttp.Config) { | 192 | g := newGatedRig(t, func(c *githttp.Config) { | ||
| 191 | c.MaxUploadPacks, c.MaxReceivePacks, c.MaxPerClient = 8, 8, 8 | 193 | c.MaxUploadPacks, c.MaxReceivePacks, c.MaxPerClient = 8, 8, 8 | ||
| 192 | tt.cfg(c) | 194 | tt.cfg(c) | ||
| @@ -210,6 +212,7 @@ func TestHandlerBackendLimits(t *testing.T) { | |||||
| 210 | } | 212 | } | ||
| 211 | 213 | ||||
| 212 | func TestHandlerBackendSlotsAreReleased(t *testing.T) { | 214 | func TestHandlerBackendSlotsAreReleased(t *testing.T) { | ||
| 215 | + | t.Parallel() | |||
| 213 | g := newGatedRig(t, func(c *githttp.Config) { | 216 | g := newGatedRig(t, func(c *githttp.Config) { | ||
| 214 | c.MaxUploadPacks, c.MaxReceivePacks, c.MaxPerClient = 1, 1, 1 | 217 | c.MaxUploadPacks, c.MaxReceivePacks, c.MaxPerClient = 1, 1, 1 | ||
| 215 | }) | 218 | }) | ||
| @@ -230,6 +233,7 @@ func TestHandlerBackendSlotsAreReleased(t *testing.T) { | |||||
| 230 | } | 233 | } | ||
| 231 | 234 | ||||
| 232 | func TestHandlerBackendFailureReleasesSlots(t *testing.T) { | 235 | func TestHandlerBackendFailureReleasesSlots(t *testing.T) { | ||
| 236 | + | t.Parallel() | |||
| 233 | r := newRig(t, func(c *githttp.Config) { c.MaxUploadPacks, c.MaxPerClient = 1, 1 }) | 237 | r := newRig(t, func(c *githttp.Config) { c.MaxUploadPacks, c.MaxPerClient = 1, 1 }) | ||
| 234 | r.backendErr = context.DeadlineExceeded | 238 | r.backendErr = context.DeadlineExceeded | ||
| 235 | for range 3 { | 239 | for range 3 { | ||
| @@ -251,6 +255,7 @@ func (d *deadlineRecorder) SetReadDeadline(t time.Time) error { | |||||
| 251 | } | 255 | } | ||
| 252 | 256 | ||||
| 253 | func TestHandlerBodyReadDeadline(t *testing.T) { | 257 | func TestHandlerBodyReadDeadline(t *testing.T) { | ||
| 258 | + | t.Parallel() | |||
| 254 | tests := []struct { | 259 | tests := []struct { | ||
| 255 | name string | 260 | name string | ||
| 256 | path string | 261 | path string | ||
| @@ -262,6 +267,7 @@ func TestHandlerBodyReadDeadline(t *testing.T) { | |||||
| 262 | } | 267 | } | ||
| 263 | for _, tt := range tests { | 268 | for _, tt := range tests { | ||
| 264 | t.Run(tt.name, func(t *testing.T) { | 269 | t.Run(tt.name, func(t *testing.T) { | ||
| 270 | + | t.Parallel() | |||
| 265 | r := newRig(t, nil) | 271 | r := newRig(t, nil) | ||
| 266 | req := httptest.NewRequest(http.MethodPost, tt.path, stringsReader("payload")) | 272 | req := httptest.NewRequest(http.MethodPost, tt.path, stringsReader("payload")) | ||
| 267 | req.SetBasicAuth("u", "root-secret") | 273 | req.SetBasicAuth("u", "root-secret") | ||
M internal/adapters/rpc/convert_test.go +16 -0
| @@ -23,7 +23,9 @@ import ( | |||
| 23 | 23 | // UNSPECIFIED, and the mappers that have an inverse round-trip. A value added | |
| 24 | 24 | // to the domain and forgotten here would reach clients as "not set". | |
| 25 | 25 | func TestEnumMappersCoverEveryDomainValue(t *testing.T) { | |
| 26 | + | t.Parallel() | |
| 26 | 27 | t.Run("visibility", func(t *testing.T) { | |
| 28 | + | t.Parallel() | |
| 27 | 29 | for _, v := range []domain.Visibility{domain.VisibilityPublic, domain.VisibilityPrivate} { | |
| 28 | 30 | got := visibilityToPB(v) | |
| 29 | 31 | assert.NotEqual(t, pb.Visibility_VISIBILITY_UNSPECIFIED, got, v) | |
| @@ -37,6 +39,7 @@ func TestEnumMappersCoverEveryDomainValue(t *testing.T) { | |||
| 37 | 39 | }) | |
| 38 | 40 | ||
| 39 | 41 | t.Run("scope", func(t *testing.T) { | |
| 42 | + | t.Parallel() | |
| 40 | 43 | for _, s := range domain.AllScopes() { | |
| 41 | 44 | got, ok := scopeToPB[s] | |
| 42 | 45 | require.True(t, ok, "scope %s has no counterpart in the contract", s) | |
| @@ -58,6 +61,7 @@ func TestEnumMappersCoverEveryDomainValue(t *testing.T) { | |||
| 58 | 61 | }) | |
| 59 | 62 | ||
| 60 | 63 | t.Run("ref kind", func(t *testing.T) { | |
| 64 | + | t.Parallel() | |
| 61 | 65 | for _, k := range []domain.RefKind{domain.RefKindHead, domain.RefKindBranch, domain.RefKindTag} { | |
| 62 | 66 | got := refKindToPB(k) | |
| 63 | 67 | assert.NotEqual(t, pb.RefKind_REF_KIND_UNSPECIFIED, got, k) | |
| @@ -70,6 +74,7 @@ func TestEnumMappersCoverEveryDomainValue(t *testing.T) { | |||
| 70 | 74 | }) | |
| 71 | 75 | ||
| 72 | 76 | t.Run("entry kind", func(t *testing.T) { | |
| 77 | + | t.Parallel() | |
| 73 | 78 | seen := map[pb.EntryKind]domain.EntryKind{} | |
| 74 | 79 | for _, k := range []domain.EntryKind{ | |
| 75 | 80 | domain.EntryFile, domain.EntryExecutable, domain.EntryDirectory, domain.EntrySymlink, domain.EntrySubmodule, | |
| @@ -82,6 +87,7 @@ func TestEnumMappersCoverEveryDomainValue(t *testing.T) { | |||
| 82 | 87 | }) | |
| 83 | 88 | ||
| 84 | 89 | t.Run("file status", func(t *testing.T) { | |
| 90 | + | t.Parallel() | |
| 85 | 91 | seen := map[pb.FileStatus]domain.FileStatus{} | |
| 86 | 92 | for _, s := range []domain.FileStatus{ | |
| 87 | 93 | domain.FileAdded, domain.FileDeleted, domain.FileModified, domain.FileRenamed, domain.FileCopied, domain.FileTypeChanged, | |
| @@ -94,6 +100,7 @@ func TestEnumMappersCoverEveryDomainValue(t *testing.T) { | |||
| 94 | 100 | }) | |
| 95 | 101 | ||
| 96 | 102 | t.Run("line kind", func(t *testing.T) { | |
| 103 | + | t.Parallel() | |
| 97 | 104 | seen := map[pb.LineKind]domain.LineKind{} | |
| 98 | 105 | for _, k := range []domain.LineKind{domain.LineContext, domain.LineAddition, domain.LineDeletion} { | |
| 99 | 106 | got := lineKindToPB(k) | |
| @@ -106,6 +113,7 @@ func TestEnumMappersCoverEveryDomainValue(t *testing.T) { | |||
| 106 | 113 | // Every value of the contract except UNSPECIFIED is produced by some value | |
| 107 | 114 | // of the domain: nothing in the contract is unreachable. | |
| 108 | 115 | t.Run("the contract has no value the domain cannot produce", func(t *testing.T) { | |
| 116 | + | t.Parallel() | |
| 109 | 117 | produced := func(names map[int32]string, from func(v int32) bool) { | |
| 110 | 118 | for v, n := range names { | |
| 111 | 119 | if v == 0 { | |
| @@ -142,6 +150,7 @@ func TestEnumMappersCoverEveryDomainValue(t *testing.T) { | |||
| 142 | 150 | } | |
| 143 | 151 | ||
| 144 | 152 | func TestMergeModeMappers(t *testing.T) { | |
| 153 | + | t.Parallel() | |
| 145 | 154 | for _, m := range domain.MergeModes() { | |
| 146 | 155 | got := mergeModeToPB(m) | |
| 147 | 156 | assert.NotEqual(t, pb.MergeMode_MERGE_MODE_UNSPECIFIED, got, m) | |
| @@ -168,6 +177,7 @@ func TestMergeModeMappers(t *testing.T) { | |||
| 168 | 177 | } | |
| 169 | 178 | ||
| 170 | 179 | func TestMergeRequestEnumMappers(t *testing.T) { | |
| 180 | + | t.Parallel() | |
| 171 | 181 | for _, s := range []domain.MRState{domain.MRStateOpen, domain.MRStateMerged, domain.MRStateClosed} { | |
| 172 | 182 | got := mergeRequestStateToPB(s) | |
| 173 | 183 | assert.NotEqual(t, pb.MergeRequestState_MERGE_REQUEST_STATE_UNSPECIFIED, got, s) | |
| @@ -192,6 +202,7 @@ func TestMergeRequestEnumMappers(t *testing.T) { | |||
| 192 | 202 | } | |
| 193 | 203 | ||
| 194 | 204 | func TestCommentID(t *testing.T) { | |
| 205 | + | t.Parallel() | |
| 195 | 206 | assert.EqualValues(t, 7, commentID("7")) | |
| 196 | 207 | for _, bad := range []string{"", "0", "-1", "07", "+7", "x", "7 ", "9223372036854775808"} { | |
| 197 | 208 | assert.Zero(t, commentID(bad), "%q names no comment", bad) | |
| @@ -199,6 +210,7 @@ func TestCommentID(t *testing.T) { | |||
| 199 | 210 | } | |
| 200 | 211 | ||
| 201 | 212 | func TestRepoToPBAlwaysHasSettings(t *testing.T) { | |
| 213 | + | t.Parallel() | |
| 202 | 214 | // A repository value without settings (never stored with them) reads as the defaults. | |
| 203 | 215 | r := repoToPB(app.RepoView{Repo: domain.Repo{Name: mustName(t, "x")}}) | |
| 204 | 216 | assert.Equal(t, []pb.MergeMode{pb.MergeMode_MERGE_MODE_FAST_FORWARD, pb.MergeMode_MERGE_MODE_SQUASH}, r.GetSettings().GetAllowedMergeModes()) | |
| @@ -218,6 +230,7 @@ func mustName(t *testing.T, s string) domain.RepoName { | |||
| 218 | 230 | } | |
| 219 | 231 | ||
| 220 | 232 | func TestUpdateParamsSettingsPaths(t *testing.T) { | |
| 233 | + | t.Parallel() | |
| 221 | 234 | msg := func(paths ...string) *pb.UpdateRepoRequest { | |
| 222 | 235 | return &pb.UpdateRepoRequest{ | |
| 223 | 236 | UpdateMask: &fieldmaskpb.FieldMask{Paths: paths}, | |
| @@ -255,6 +268,7 @@ func TestUpdateParamsSettingsPaths(t *testing.T) { | |||
| 255 | 268 | } | |
| 256 | 269 | ||
| 257 | 270 | func TestConnectErrorCarriesTheFieldsOfARejection(t *testing.T) { | |
| 271 | + | t.Parallel() | |
| 258 | 272 | var v domain.Violations | |
| 259 | 273 | v.Add("title", "must be one line") | |
| 260 | 274 | v.Add("source_branch", "does not exist") | |
| @@ -282,6 +296,7 @@ func TestConnectErrorCarriesTheFieldsOfARejection(t *testing.T) { | |||
| 282 | 296 | } | |
| 283 | 297 | ||
| 284 | 298 | func TestWatchEventToPBMergeRequestEvents(t *testing.T) { | |
| 299 | + | t.Parallel() | |
| 285 | 300 | repo := mustName(t, "proj") | |
| 286 | 301 | title, err := domain.ParseMRTitle("Add x") | |
| 287 | 302 | require.NoError(t, err) | |
| @@ -297,6 +312,7 @@ func TestWatchEventToPBMergeRequestEvents(t *testing.T) { | |||
| 297 | 312 | "closed": {domain.MergeRequestClosed{MergeRequest: mr, At: at}, func(e *pb.Event) *pb.MergeRequest { return e.GetMergeRequestClosed().GetMergeRequest() }}, | |
| 298 | 313 | } { | |
| 299 | 314 | t.Run(name, func(t *testing.T) { | |
| 315 | + | t.Parallel() | |
| 300 | 316 | got := watchEventToPB(app.WatchEvent{ID: "e1", Event: tc.ev}) | |
| 301 | 317 | require.NotNil(t, got) | |
| 302 | 318 | assert.Equal(t, "e1", got.GetId()) | |
| @@ -23,7 +23,9 @@ import ( | |||||
| 23 | // UNSPECIFIED, and the mappers that have an inverse round-trip. A value added | 23 | // UNSPECIFIED, and the mappers that have an inverse round-trip. A value added | ||
| 24 | // to the domain and forgotten here would reach clients as "not set". | 24 | // to the domain and forgotten here would reach clients as "not set". | ||
| 25 | func TestEnumMappersCoverEveryDomainValue(t *testing.T) { | 25 | func TestEnumMappersCoverEveryDomainValue(t *testing.T) { | ||
| 26 | + | t.Parallel() | |||
| 26 | t.Run("visibility", func(t *testing.T) { | 27 | t.Run("visibility", func(t *testing.T) { | ||
| 28 | + | t.Parallel() | |||
| 27 | for _, v := range []domain.Visibility{domain.VisibilityPublic, domain.VisibilityPrivate} { | 29 | for _, v := range []domain.Visibility{domain.VisibilityPublic, domain.VisibilityPrivate} { | ||
| 28 | got := visibilityToPB(v) | 30 | got := visibilityToPB(v) | ||
| 29 | assert.NotEqual(t, pb.Visibility_VISIBILITY_UNSPECIFIED, got, v) | 31 | assert.NotEqual(t, pb.Visibility_VISIBILITY_UNSPECIFIED, got, v) | ||
| @@ -37,6 +39,7 @@ func TestEnumMappersCoverEveryDomainValue(t *testing.T) { | |||||
| 37 | }) | 39 | }) | ||
| 38 | 40 | ||||
| 39 | t.Run("scope", func(t *testing.T) { | 41 | t.Run("scope", func(t *testing.T) { | ||
| 42 | + | t.Parallel() | |||
| 40 | for _, s := range domain.AllScopes() { | 43 | for _, s := range domain.AllScopes() { | ||
| 41 | got, ok := scopeToPB[s] | 44 | got, ok := scopeToPB[s] | ||
| 42 | require.True(t, ok, "scope %s has no counterpart in the contract", s) | 45 | require.True(t, ok, "scope %s has no counterpart in the contract", s) | ||
| @@ -58,6 +61,7 @@ func TestEnumMappersCoverEveryDomainValue(t *testing.T) { | |||||
| 58 | }) | 61 | }) | ||
| 59 | 62 | ||||
| 60 | t.Run("ref kind", func(t *testing.T) { | 63 | t.Run("ref kind", func(t *testing.T) { | ||
| 64 | + | t.Parallel() | |||
| 61 | for _, k := range []domain.RefKind{domain.RefKindHead, domain.RefKindBranch, domain.RefKindTag} { | 65 | for _, k := range []domain.RefKind{domain.RefKindHead, domain.RefKindBranch, domain.RefKindTag} { | ||
| 62 | got := refKindToPB(k) | 66 | got := refKindToPB(k) | ||
| 63 | assert.NotEqual(t, pb.RefKind_REF_KIND_UNSPECIFIED, got, k) | 67 | assert.NotEqual(t, pb.RefKind_REF_KIND_UNSPECIFIED, got, k) | ||
| @@ -70,6 +74,7 @@ func TestEnumMappersCoverEveryDomainValue(t *testing.T) { | |||||
| 70 | }) | 74 | }) | ||
| 71 | 75 | ||||
| 72 | t.Run("entry kind", func(t *testing.T) { | 76 | t.Run("entry kind", func(t *testing.T) { | ||
| 77 | + | t.Parallel() | |||
| 73 | seen := map[pb.EntryKind]domain.EntryKind{} | 78 | seen := map[pb.EntryKind]domain.EntryKind{} | ||
| 74 | for _, k := range []domain.EntryKind{ | 79 | for _, k := range []domain.EntryKind{ | ||
| 75 | domain.EntryFile, domain.EntryExecutable, domain.EntryDirectory, domain.EntrySymlink, domain.EntrySubmodule, | 80 | domain.EntryFile, domain.EntryExecutable, domain.EntryDirectory, domain.EntrySymlink, domain.EntrySubmodule, | ||
| @@ -82,6 +87,7 @@ func TestEnumMappersCoverEveryDomainValue(t *testing.T) { | |||||
| 82 | }) | 87 | }) | ||
| 83 | 88 | ||||
| 84 | t.Run("file status", func(t *testing.T) { | 89 | t.Run("file status", func(t *testing.T) { | ||
| 90 | + | t.Parallel() | |||
| 85 | seen := map[pb.FileStatus]domain.FileStatus{} | 91 | seen := map[pb.FileStatus]domain.FileStatus{} | ||
| 86 | for _, s := range []domain.FileStatus{ | 92 | for _, s := range []domain.FileStatus{ | ||
| 87 | domain.FileAdded, domain.FileDeleted, domain.FileModified, domain.FileRenamed, domain.FileCopied, domain.FileTypeChanged, | 93 | domain.FileAdded, domain.FileDeleted, domain.FileModified, domain.FileRenamed, domain.FileCopied, domain.FileTypeChanged, | ||
| @@ -94,6 +100,7 @@ func TestEnumMappersCoverEveryDomainValue(t *testing.T) { | |||||
| 94 | }) | 100 | }) | ||
| 95 | 101 | ||||
| 96 | t.Run("line kind", func(t *testing.T) { | 102 | t.Run("line kind", func(t *testing.T) { | ||
| 103 | + | t.Parallel() | |||
| 97 | seen := map[pb.LineKind]domain.LineKind{} | 104 | seen := map[pb.LineKind]domain.LineKind{} | ||
| 98 | for _, k := range []domain.LineKind{domain.LineContext, domain.LineAddition, domain.LineDeletion} { | 105 | for _, k := range []domain.LineKind{domain.LineContext, domain.LineAddition, domain.LineDeletion} { | ||
| 99 | got := lineKindToPB(k) | 106 | got := lineKindToPB(k) | ||
| @@ -106,6 +113,7 @@ func TestEnumMappersCoverEveryDomainValue(t *testing.T) { | |||||
| 106 | // Every value of the contract except UNSPECIFIED is produced by some value | 113 | // Every value of the contract except UNSPECIFIED is produced by some value | ||
| 107 | // of the domain: nothing in the contract is unreachable. | 114 | // of the domain: nothing in the contract is unreachable. | ||
| 108 | t.Run("the contract has no value the domain cannot produce", func(t *testing.T) { | 115 | t.Run("the contract has no value the domain cannot produce", func(t *testing.T) { | ||
| 116 | + | t.Parallel() | |||
| 109 | produced := func(names map[int32]string, from func(v int32) bool) { | 117 | produced := func(names map[int32]string, from func(v int32) bool) { | ||
| 110 | for v, n := range names { | 118 | for v, n := range names { | ||
| 111 | if v == 0 { | 119 | if v == 0 { | ||
| @@ -142,6 +150,7 @@ func TestEnumMappersCoverEveryDomainValue(t *testing.T) { | |||||
| 142 | } | 150 | } | ||
| 143 | 151 | ||||
| 144 | func TestMergeModeMappers(t *testing.T) { | 152 | func TestMergeModeMappers(t *testing.T) { | ||
| 153 | + | t.Parallel() | |||
| 145 | for _, m := range domain.MergeModes() { | 154 | for _, m := range domain.MergeModes() { | ||
| 146 | got := mergeModeToPB(m) | 155 | got := mergeModeToPB(m) | ||
| 147 | assert.NotEqual(t, pb.MergeMode_MERGE_MODE_UNSPECIFIED, got, m) | 156 | assert.NotEqual(t, pb.MergeMode_MERGE_MODE_UNSPECIFIED, got, m) | ||
| @@ -168,6 +177,7 @@ func TestMergeModeMappers(t *testing.T) { | |||||
| 168 | } | 177 | } | ||
| 169 | 178 | ||||
| 170 | func TestMergeRequestEnumMappers(t *testing.T) { | 179 | func TestMergeRequestEnumMappers(t *testing.T) { | ||
| 180 | + | t.Parallel() | |||
| 171 | for _, s := range []domain.MRState{domain.MRStateOpen, domain.MRStateMerged, domain.MRStateClosed} { | 181 | for _, s := range []domain.MRState{domain.MRStateOpen, domain.MRStateMerged, domain.MRStateClosed} { | ||
| 172 | got := mergeRequestStateToPB(s) | 182 | got := mergeRequestStateToPB(s) | ||
| 173 | assert.NotEqual(t, pb.MergeRequestState_MERGE_REQUEST_STATE_UNSPECIFIED, got, s) | 183 | assert.NotEqual(t, pb.MergeRequestState_MERGE_REQUEST_STATE_UNSPECIFIED, got, s) | ||
| @@ -192,6 +202,7 @@ func TestMergeRequestEnumMappers(t *testing.T) { | |||||
| 192 | } | 202 | } | ||
| 193 | 203 | ||||
| 194 | func TestCommentID(t *testing.T) { | 204 | func TestCommentID(t *testing.T) { | ||
| 205 | + | t.Parallel() | |||
| 195 | assert.EqualValues(t, 7, commentID("7")) | 206 | assert.EqualValues(t, 7, commentID("7")) | ||
| 196 | for _, bad := range []string{"", "0", "-1", "07", "+7", "x", "7 ", "9223372036854775808"} { | 207 | for _, bad := range []string{"", "0", "-1", "07", "+7", "x", "7 ", "9223372036854775808"} { | ||
| 197 | assert.Zero(t, commentID(bad), "%q names no comment", bad) | 208 | assert.Zero(t, commentID(bad), "%q names no comment", bad) | ||
| @@ -199,6 +210,7 @@ func TestCommentID(t *testing.T) { | |||||
| 199 | } | 210 | } | ||
| 200 | 211 | ||||
| 201 | func TestRepoToPBAlwaysHasSettings(t *testing.T) { | 212 | func TestRepoToPBAlwaysHasSettings(t *testing.T) { | ||
| 213 | + | t.Parallel() | |||
| 202 | // A repository value without settings (never stored with them) reads as the defaults. | 214 | // A repository value without settings (never stored with them) reads as the defaults. | ||
| 203 | r := repoToPB(app.RepoView{Repo: domain.Repo{Name: mustName(t, "x")}}) | 215 | r := repoToPB(app.RepoView{Repo: domain.Repo{Name: mustName(t, "x")}}) | ||
| 204 | assert.Equal(t, []pb.MergeMode{pb.MergeMode_MERGE_MODE_FAST_FORWARD, pb.MergeMode_MERGE_MODE_SQUASH}, r.GetSettings().GetAllowedMergeModes()) | 216 | assert.Equal(t, []pb.MergeMode{pb.MergeMode_MERGE_MODE_FAST_FORWARD, pb.MergeMode_MERGE_MODE_SQUASH}, r.GetSettings().GetAllowedMergeModes()) | ||
| @@ -218,6 +230,7 @@ func mustName(t *testing.T, s string) domain.RepoName { | |||||
| 218 | } | 230 | } | ||
| 219 | 231 | ||||
| 220 | func TestUpdateParamsSettingsPaths(t *testing.T) { | 232 | func TestUpdateParamsSettingsPaths(t *testing.T) { | ||
| 233 | + | t.Parallel() | |||
| 221 | msg := func(paths ...string) *pb.UpdateRepoRequest { | 234 | msg := func(paths ...string) *pb.UpdateRepoRequest { | ||
| 222 | return &pb.UpdateRepoRequest{ | 235 | return &pb.UpdateRepoRequest{ | ||
| 223 | UpdateMask: &fieldmaskpb.FieldMask{Paths: paths}, | 236 | UpdateMask: &fieldmaskpb.FieldMask{Paths: paths}, | ||
| @@ -255,6 +268,7 @@ func TestUpdateParamsSettingsPaths(t *testing.T) { | |||||
| 255 | } | 268 | } | ||
| 256 | 269 | ||||
| 257 | func TestConnectErrorCarriesTheFieldsOfARejection(t *testing.T) { | 270 | func TestConnectErrorCarriesTheFieldsOfARejection(t *testing.T) { | ||
| 271 | + | t.Parallel() | |||
| 258 | var v domain.Violations | 272 | var v domain.Violations | ||
| 259 | v.Add("title", "must be one line") | 273 | v.Add("title", "must be one line") | ||
| 260 | v.Add("source_branch", "does not exist") | 274 | v.Add("source_branch", "does not exist") | ||
| @@ -282,6 +296,7 @@ func TestConnectErrorCarriesTheFieldsOfARejection(t *testing.T) { | |||||
| 282 | } | 296 | } | ||
| 283 | 297 | ||||
| 284 | func TestWatchEventToPBMergeRequestEvents(t *testing.T) { | 298 | func TestWatchEventToPBMergeRequestEvents(t *testing.T) { | ||
| 299 | + | t.Parallel() | |||
| 285 | repo := mustName(t, "proj") | 300 | repo := mustName(t, "proj") | ||
| 286 | title, err := domain.ParseMRTitle("Add x") | 301 | title, err := domain.ParseMRTitle("Add x") | ||
| 287 | require.NoError(t, err) | 302 | require.NoError(t, err) | ||
| @@ -297,6 +312,7 @@ func TestWatchEventToPBMergeRequestEvents(t *testing.T) { | |||||
| 297 | "closed": {domain.MergeRequestClosed{MergeRequest: mr, At: at}, func(e *pb.Event) *pb.MergeRequest { return e.GetMergeRequestClosed().GetMergeRequest() }}, | 312 | "closed": {domain.MergeRequestClosed{MergeRequest: mr, At: at}, func(e *pb.Event) *pb.MergeRequest { return e.GetMergeRequestClosed().GetMergeRequest() }}, | ||
| 298 | } { | 313 | } { | ||
| 299 | t.Run(name, func(t *testing.T) { | 314 | t.Run(name, func(t *testing.T) { | ||
| 315 | + | t.Parallel() | |||
| 300 | got := watchEventToPB(app.WatchEvent{ID: "e1", Event: tc.ev}) | 316 | got := watchEventToPB(app.WatchEvent{ID: "e1", Event: tc.ev}) | ||
| 301 | require.NotNil(t, got) | 317 | require.NotNil(t, got) | ||
| 302 | assert.Equal(t, "e1", got.GetId()) | 318 | assert.Equal(t, "e1", got.GetId()) | ||
M internal/adapters/rpc/git_test.go +13 -0
| @@ -17,6 +17,7 @@ import ( | |||
| 17 | 17 | ) | |
| 18 | 18 | ||
| 19 | 19 | func TestGitServiceGetTree(t *testing.T) { | |
| 20 | + | t.Parallel() | |
| 20 | 21 | tests := []struct { | |
| 21 | 22 | name string | |
| 22 | 23 | req *pb.GetTreeRequest | |
| @@ -32,6 +33,7 @@ func TestGitServiceGetTree(t *testing.T) { | |||
| 32 | 33 | } | |
| 33 | 34 | for _, tt := range tests { | |
| 34 | 35 | t.Run(tt.name, func(t *testing.T) { | |
| 36 | + | t.Parallel() | |
| 35 | 37 | browse := mocks.NewBrowse(t) | |
| 36 | 38 | if tt.wantCall { | |
| 37 | 39 | browse.EXPECT().GetTree(mock.Anything, domain.Owner(), mustRepoName(t, "demo"), mock.Anything, mock.Anything, false). | |
| @@ -49,6 +51,7 @@ func TestGitServiceGetTree(t *testing.T) { | |||
| 49 | 51 | } | |
| 50 | 52 | ||
| 51 | 53 | func TestGitServiceListRefs(t *testing.T) { | |
| 54 | + | t.Parallel() | |
| 52 | 55 | tests := []struct { | |
| 53 | 56 | name string | |
| 54 | 57 | req *pb.ListRefsRequest | |
| @@ -64,6 +67,7 @@ func TestGitServiceListRefs(t *testing.T) { | |||
| 64 | 67 | } | |
| 65 | 68 | for _, tt := range tests { | |
| 66 | 69 | t.Run(tt.name, func(t *testing.T) { | |
| 70 | + | t.Parallel() | |
| 67 | 71 | browse := mocks.NewBrowse(t) | |
| 68 | 72 | if tt.wantCall { | |
| 69 | 73 | browse.EXPECT().ListRefs(mock.Anything, domain.Owner(), mustRepoName(t, "demo"), mock.Anything). | |
| @@ -81,28 +85,33 @@ func TestGitServiceListRefs(t *testing.T) { | |||
| 81 | 85 | } | |
| 82 | 86 | ||
| 83 | 87 | func TestGitServiceGetCommitAndResolveRevision(t *testing.T) { | |
| 88 | + | t.Parallel() | |
| 84 | 89 | invalid := []struct{ name, repo, rev string }{ | |
| 85 | 90 | {"invalid repo", "../x", "main"}, | |
| 86 | 91 | {"invalid revision", "demo", "a b"}, | |
| 87 | 92 | } | |
| 88 | 93 | for _, tt := range invalid { | |
| 89 | 94 | t.Run("GetCommit "+tt.name, func(t *testing.T) { | |
| 95 | + | t.Parallel() | |
| 90 | 96 | _, err := (&gitService{mocks.NewBrowse(t)}).GetCommit(ownerCtx(), connect.NewRequest(&pb.GetCommitRequest{Repo: tt.repo, Revision: tt.rev})) | |
| 91 | 97 | requireCode(t, connect.CodeInvalidArgument, err) | |
| 92 | 98 | }) | |
| 93 | 99 | t.Run("ResolveRevision "+tt.name, func(t *testing.T) { | |
| 100 | + | t.Parallel() | |
| 94 | 101 | _, err := (&gitService{mocks.NewBrowse(t)}).ResolveRevision(ownerCtx(), connect.NewRequest(&pb.ResolveRevisionRequest{Repo: tt.repo, Revision: tt.rev})) | |
| 95 | 102 | requireCode(t, connect.CodeInvalidArgument, err) | |
| 96 | 103 | }) | |
| 97 | 104 | } | |
| 98 | 105 | ||
| 99 | 106 | t.Run("GetCommit not found", func(t *testing.T) { | |
| 107 | + | t.Parallel() | |
| 100 | 108 | browse := mocks.NewBrowse(t) | |
| 101 | 109 | browse.EXPECT().GetCommit(mock.Anything, domain.Owner(), mustRepoName(t, "demo"), mock.Anything).Return(domain.CommitDetail{}, domain.ErrNotFound).Once() | |
| 102 | 110 | _, err := (&gitService{browse}).GetCommit(ownerCtx(), connect.NewRequest(&pb.GetCommitRequest{Repo: "demo", Revision: "main"})) | |
| 103 | 111 | requireCode(t, connect.CodeNotFound, err) | |
| 104 | 112 | }) | |
| 105 | 113 | t.Run("ResolveRevision ok", func(t *testing.T) { | |
| 114 | + | t.Parallel() | |
| 106 | 115 | browse := mocks.NewBrowse(t) | |
| 107 | 116 | browse.EXPECT().ResolveRevision(mock.Anything, domain.Owner(), mustRepoName(t, "demo"), mock.Anything). | |
| 108 | 117 | Return(domain.ResolvedRevision{Ref: "refs/heads/main"}, nil).Once() | |
| @@ -113,6 +122,7 @@ func TestGitServiceGetCommitAndResolveRevision(t *testing.T) { | |||
| 113 | 122 | } | |
| 114 | 123 | ||
| 115 | 124 | func TestGitServiceListCommits(t *testing.T) { | |
| 125 | + | t.Parallel() | |
| 116 | 126 | tests := []struct { | |
| 117 | 127 | name string | |
| 118 | 128 | req *pb.ListCommitsRequest | |
| @@ -127,6 +137,7 @@ func TestGitServiceListCommits(t *testing.T) { | |||
| 127 | 137 | } | |
| 128 | 138 | for _, tt := range tests { | |
| 129 | 139 | t.Run(tt.name, func(t *testing.T) { | |
| 140 | + | t.Parallel() | |
| 130 | 141 | browse := mocks.NewBrowse(t) | |
| 131 | 142 | if tt.wantCall { | |
| 132 | 143 | browse.EXPECT().ListCommits(mock.Anything, domain.Owner(), mustRepoName(t, "demo"), mock.Anything). | |
| @@ -144,6 +155,7 @@ func TestGitServiceListCommits(t *testing.T) { | |||
| 144 | 155 | ||
| 145 | 156 | // GetBlob validates before it opens anything or touches the stream. | |
| 146 | 157 | func TestGitServiceGetBlobRejectsBadInput(t *testing.T) { | |
| 158 | + | t.Parallel() | |
| 147 | 159 | tests := []struct { | |
| 148 | 160 | name string | |
| 149 | 161 | req *pb.GetBlobRequest | |
| @@ -154,6 +166,7 @@ func TestGitServiceGetBlobRejectsBadInput(t *testing.T) { | |||
| 154 | 166 | } | |
| 155 | 167 | for _, tt := range tests { | |
| 156 | 168 | t.Run(tt.name, func(t *testing.T) { | |
| 169 | + | t.Parallel() | |
| 157 | 170 | err := (&gitService{mocks.NewBrowse(t)}).GetBlob(ownerCtx(), connect.NewRequest(tt.req), nil) | |
| 158 | 171 | requireCode(t, connect.CodeInvalidArgument, err) | |
| 159 | 172 | }) | |
| @@ -17,6 +17,7 @@ import ( | |||||
| 17 | ) | 17 | ) | ||
| 18 | 18 | ||||
| 19 | func TestGitServiceGetTree(t *testing.T) { | 19 | func TestGitServiceGetTree(t *testing.T) { | ||
| 20 | + | t.Parallel() | |||
| 20 | tests := []struct { | 21 | tests := []struct { | ||
| 21 | name string | 22 | name string | ||
| 22 | req *pb.GetTreeRequest | 23 | req *pb.GetTreeRequest | ||
| @@ -32,6 +33,7 @@ func TestGitServiceGetTree(t *testing.T) { | |||||
| 32 | } | 33 | } | ||
| 33 | for _, tt := range tests { | 34 | for _, tt := range tests { | ||
| 34 | t.Run(tt.name, func(t *testing.T) { | 35 | t.Run(tt.name, func(t *testing.T) { | ||
| 36 | + | t.Parallel() | |||
| 35 | browse := mocks.NewBrowse(t) | 37 | browse := mocks.NewBrowse(t) | ||
| 36 | if tt.wantCall { | 38 | if tt.wantCall { | ||
| 37 | browse.EXPECT().GetTree(mock.Anything, domain.Owner(), mustRepoName(t, "demo"), mock.Anything, mock.Anything, false). | 39 | browse.EXPECT().GetTree(mock.Anything, domain.Owner(), mustRepoName(t, "demo"), mock.Anything, mock.Anything, false). | ||
| @@ -49,6 +51,7 @@ func TestGitServiceGetTree(t *testing.T) { | |||||
| 49 | } | 51 | } | ||
| 50 | 52 | ||||
| 51 | func TestGitServiceListRefs(t *testing.T) { | 53 | func TestGitServiceListRefs(t *testing.T) { | ||
| 54 | + | t.Parallel() | |||
| 52 | tests := []struct { | 55 | tests := []struct { | ||
| 53 | name string | 56 | name string | ||
| 54 | req *pb.ListRefsRequest | 57 | req *pb.ListRefsRequest | ||
| @@ -64,6 +67,7 @@ func TestGitServiceListRefs(t *testing.T) { | |||||
| 64 | } | 67 | } | ||
| 65 | for _, tt := range tests { | 68 | for _, tt := range tests { | ||
| 66 | t.Run(tt.name, func(t *testing.T) { | 69 | t.Run(tt.name, func(t *testing.T) { | ||
| 70 | + | t.Parallel() | |||
| 67 | browse := mocks.NewBrowse(t) | 71 | browse := mocks.NewBrowse(t) | ||
| 68 | if tt.wantCall { | 72 | if tt.wantCall { | ||
| 69 | browse.EXPECT().ListRefs(mock.Anything, domain.Owner(), mustRepoName(t, "demo"), mock.Anything). | 73 | browse.EXPECT().ListRefs(mock.Anything, domain.Owner(), mustRepoName(t, "demo"), mock.Anything). | ||
| @@ -81,28 +85,33 @@ func TestGitServiceListRefs(t *testing.T) { | |||||
| 81 | } | 85 | } | ||
| 82 | 86 | ||||
| 83 | func TestGitServiceGetCommitAndResolveRevision(t *testing.T) { | 87 | func TestGitServiceGetCommitAndResolveRevision(t *testing.T) { | ||
| 88 | + | t.Parallel() | |||
| 84 | invalid := []struct{ name, repo, rev string }{ | 89 | invalid := []struct{ name, repo, rev string }{ | ||
| 85 | {"invalid repo", "../x", "main"}, | 90 | {"invalid repo", "../x", "main"}, | ||
| 86 | {"invalid revision", "demo", "a b"}, | 91 | {"invalid revision", "demo", "a b"}, | ||
| 87 | } | 92 | } | ||
| 88 | for _, tt := range invalid { | 93 | for _, tt := range invalid { | ||
| 89 | t.Run("GetCommit "+tt.name, func(t *testing.T) { | 94 | t.Run("GetCommit "+tt.name, func(t *testing.T) { | ||
| 95 | + | t.Parallel() | |||
| 90 | _, err := (&gitService{mocks.NewBrowse(t)}).GetCommit(ownerCtx(), connect.NewRequest(&pb.GetCommitRequest{Repo: tt.repo, Revision: tt.rev})) | 96 | _, err := (&gitService{mocks.NewBrowse(t)}).GetCommit(ownerCtx(), connect.NewRequest(&pb.GetCommitRequest{Repo: tt.repo, Revision: tt.rev})) | ||
| 91 | requireCode(t, connect.CodeInvalidArgument, err) | 97 | requireCode(t, connect.CodeInvalidArgument, err) | ||
| 92 | }) | 98 | }) | ||
| 93 | t.Run("ResolveRevision "+tt.name, func(t *testing.T) { | 99 | t.Run("ResolveRevision "+tt.name, func(t *testing.T) { | ||
| 100 | + | t.Parallel() | |||
| 94 | _, err := (&gitService{mocks.NewBrowse(t)}).ResolveRevision(ownerCtx(), connect.NewRequest(&pb.ResolveRevisionRequest{Repo: tt.repo, Revision: tt.rev})) | 101 | _, err := (&gitService{mocks.NewBrowse(t)}).ResolveRevision(ownerCtx(), connect.NewRequest(&pb.ResolveRevisionRequest{Repo: tt.repo, Revision: tt.rev})) | ||
| 95 | requireCode(t, connect.CodeInvalidArgument, err) | 102 | requireCode(t, connect.CodeInvalidArgument, err) | ||
| 96 | }) | 103 | }) | ||
| 97 | } | 104 | } | ||
| 98 | 105 | ||||
| 99 | t.Run("GetCommit not found", func(t *testing.T) { | 106 | t.Run("GetCommit not found", func(t *testing.T) { | ||
| 107 | + | t.Parallel() | |||
| 100 | browse := mocks.NewBrowse(t) | 108 | browse := mocks.NewBrowse(t) | ||
| 101 | browse.EXPECT().GetCommit(mock.Anything, domain.Owner(), mustRepoName(t, "demo"), mock.Anything).Return(domain.CommitDetail{}, domain.ErrNotFound).Once() | 109 | browse.EXPECT().GetCommit(mock.Anything, domain.Owner(), mustRepoName(t, "demo"), mock.Anything).Return(domain.CommitDetail{}, domain.ErrNotFound).Once() | ||
| 102 | _, err := (&gitService{browse}).GetCommit(ownerCtx(), connect.NewRequest(&pb.GetCommitRequest{Repo: "demo", Revision: "main"})) | 110 | _, err := (&gitService{browse}).GetCommit(ownerCtx(), connect.NewRequest(&pb.GetCommitRequest{Repo: "demo", Revision: "main"})) | ||
| 103 | requireCode(t, connect.CodeNotFound, err) | 111 | requireCode(t, connect.CodeNotFound, err) | ||
| 104 | }) | 112 | }) | ||
| 105 | t.Run("ResolveRevision ok", func(t *testing.T) { | 113 | t.Run("ResolveRevision ok", func(t *testing.T) { | ||
| 114 | + | t.Parallel() | |||
| 106 | browse := mocks.NewBrowse(t) | 115 | browse := mocks.NewBrowse(t) | ||
| 107 | browse.EXPECT().ResolveRevision(mock.Anything, domain.Owner(), mustRepoName(t, "demo"), mock.Anything). | 116 | browse.EXPECT().ResolveRevision(mock.Anything, domain.Owner(), mustRepoName(t, "demo"), mock.Anything). | ||
| 108 | Return(domain.ResolvedRevision{Ref: "refs/heads/main"}, nil).Once() | 117 | Return(domain.ResolvedRevision{Ref: "refs/heads/main"}, nil).Once() | ||
| @@ -113,6 +122,7 @@ func TestGitServiceGetCommitAndResolveRevision(t *testing.T) { | |||||
| 113 | } | 122 | } | ||
| 114 | 123 | ||||
| 115 | func TestGitServiceListCommits(t *testing.T) { | 124 | func TestGitServiceListCommits(t *testing.T) { | ||
| 125 | + | t.Parallel() | |||
| 116 | tests := []struct { | 126 | tests := []struct { | ||
| 117 | name string | 127 | name string | ||
| 118 | req *pb.ListCommitsRequest | 128 | req *pb.ListCommitsRequest | ||
| @@ -127,6 +137,7 @@ func TestGitServiceListCommits(t *testing.T) { | |||||
| 127 | } | 137 | } | ||
| 128 | for _, tt := range tests { | 138 | for _, tt := range tests { | ||
| 129 | t.Run(tt.name, func(t *testing.T) { | 139 | t.Run(tt.name, func(t *testing.T) { | ||
| 140 | + | t.Parallel() | |||
| 130 | browse := mocks.NewBrowse(t) | 141 | browse := mocks.NewBrowse(t) | ||
| 131 | if tt.wantCall { | 142 | if tt.wantCall { | ||
| 132 | browse.EXPECT().ListCommits(mock.Anything, domain.Owner(), mustRepoName(t, "demo"), mock.Anything). | 143 | browse.EXPECT().ListCommits(mock.Anything, domain.Owner(), mustRepoName(t, "demo"), mock.Anything). | ||
| @@ -144,6 +155,7 @@ func TestGitServiceListCommits(t *testing.T) { | |||||
| 144 | 155 | ||||
| 145 | // GetBlob validates before it opens anything or touches the stream. | 156 | // GetBlob validates before it opens anything or touches the stream. | ||
| 146 | func TestGitServiceGetBlobRejectsBadInput(t *testing.T) { | 157 | func TestGitServiceGetBlobRejectsBadInput(t *testing.T) { | ||
| 158 | + | t.Parallel() | |||
| 147 | tests := []struct { | 159 | tests := []struct { | ||
| 148 | name string | 160 | name string | ||
| 149 | req *pb.GetBlobRequest | 161 | req *pb.GetBlobRequest | ||
| @@ -154,6 +166,7 @@ func TestGitServiceGetBlobRejectsBadInput(t *testing.T) { | |||||
| 154 | } | 166 | } | ||
| 155 | for _, tt := range tests { | 167 | for _, tt := range tests { | ||
| 156 | t.Run(tt.name, func(t *testing.T) { | 168 | t.Run(tt.name, func(t *testing.T) { | ||
| 169 | + | t.Parallel() | |||
| 157 | err := (&gitService{mocks.NewBrowse(t)}).GetBlob(ownerCtx(), connect.NewRequest(tt.req), nil) | 170 | err := (&gitService{mocks.NewBrowse(t)}).GetBlob(ownerCtx(), connect.NewRequest(tt.req), nil) | ||
| 158 | requireCode(t, connect.CodeInvalidArgument, err) | 171 | requireCode(t, connect.CodeInvalidArgument, err) | ||
| 159 | }) | 172 | }) | ||
M internal/adapters/rpc/interceptor_test.go +3 -0
| @@ -30,6 +30,7 @@ func bearerHeader(v string) http.Header { | |||
| 30 | 30 | } | |
| 31 | 31 | ||
| 32 | 32 | func TestInterceptorAuthenticate(t *testing.T) { | |
| 33 | + | t.Parallel() | |
| 33 | 34 | const procedure = ermiteconnect.RepoServiceListReposProcedure | |
| 34 | 35 | tests := []struct { | |
| 35 | 36 | name string | |
| @@ -67,6 +68,7 @@ func TestInterceptorAuthenticate(t *testing.T) { | |||
| 67 | 68 | } | |
| 68 | 69 | for _, tt := range tests { | |
| 69 | 70 | t.Run(tt.name, func(t *testing.T) { | |
| 71 | + | t.Parallel() | |
| 70 | 72 | auth := mocks.NewAuthenticator(t) | |
| 71 | 73 | limiter := mocks.NewFailureLimiter(t) | |
| 72 | 74 | if tt.wantLimit { | |
| @@ -110,6 +112,7 @@ func TestInterceptorAuthenticate(t *testing.T) { | |||
| 110 | 112 | } | |
| 111 | 113 | ||
| 112 | 114 | func TestInterceptorIgnoresForwardedForFromUntrustedPeer(t *testing.T) { | |
| 115 | + | t.Parallel() | |
| 113 | 116 | limiter := mocks.NewFailureLimiter(t) | |
| 114 | 117 | limiter.EXPECT().Blocked(mock.Anything, "203.0.113.7").Return(false, time.Duration(0)).Once() | |
| 115 | 118 | h := bearerHeader("") | |
| @@ -30,6 +30,7 @@ func bearerHeader(v string) http.Header { | |||||
| 30 | } | 30 | } | ||
| 31 | 31 | ||||
| 32 | func TestInterceptorAuthenticate(t *testing.T) { | 32 | func TestInterceptorAuthenticate(t *testing.T) { | ||
| 33 | + | t.Parallel() | |||
| 33 | const procedure = ermiteconnect.RepoServiceListReposProcedure | 34 | const procedure = ermiteconnect.RepoServiceListReposProcedure | ||
| 34 | tests := []struct { | 35 | tests := []struct { | ||
| 35 | name string | 36 | name string | ||
| @@ -67,6 +68,7 @@ func TestInterceptorAuthenticate(t *testing.T) { | |||||
| 67 | } | 68 | } | ||
| 68 | for _, tt := range tests { | 69 | for _, tt := range tests { | ||
| 69 | t.Run(tt.name, func(t *testing.T) { | 70 | t.Run(tt.name, func(t *testing.T) { | ||
| 71 | + | t.Parallel() | |||
| 70 | auth := mocks.NewAuthenticator(t) | 72 | auth := mocks.NewAuthenticator(t) | ||
| 71 | limiter := mocks.NewFailureLimiter(t) | 73 | limiter := mocks.NewFailureLimiter(t) | ||
| 72 | if tt.wantLimit { | 74 | if tt.wantLimit { | ||
| @@ -110,6 +112,7 @@ func TestInterceptorAuthenticate(t *testing.T) { | |||||
| 110 | } | 112 | } | ||
| 111 | 113 | ||||
| 112 | func TestInterceptorIgnoresForwardedForFromUntrustedPeer(t *testing.T) { | 114 | func TestInterceptorIgnoresForwardedForFromUntrustedPeer(t *testing.T) { | ||
| 115 | + | t.Parallel() | |||
| 113 | limiter := mocks.NewFailureLimiter(t) | 116 | limiter := mocks.NewFailureLimiter(t) | ||
| 114 | limiter.EXPECT().Blocked(mock.Anything, "203.0.113.7").Return(false, time.Duration(0)).Once() | 117 | limiter.EXPECT().Blocked(mock.Anything, "203.0.113.7").Return(false, time.Duration(0)).Once() | ||
| 115 | h := bearerHeader("") | 118 | h := bearerHeader("") | ||
M internal/adapters/rpc/mergerequest_test.go +17 -0
| @@ -17,6 +17,7 @@ import ( | |||
| 17 | 17 | ) | |
| 18 | 18 | ||
| 19 | 19 | func TestMergeRequestServiceCreate(t *testing.T) { | |
| 20 | + | t.Parallel() | |
| 20 | 21 | tests := []struct { | |
| 21 | 22 | name string | |
| 22 | 23 | repo string | |
| @@ -33,6 +34,7 @@ func TestMergeRequestServiceCreate(t *testing.T) { | |||
| 33 | 34 | } | |
| 34 | 35 | for _, tt := range tests { | |
| 35 | 36 | t.Run(tt.name, func(t *testing.T) { | |
| 37 | + | t.Parallel() | |
| 36 | 38 | mrs := mocks.NewMergeRequests(t) | |
| 37 | 39 | if tt.wantCall { | |
| 38 | 40 | mrs.EXPECT().Create(mock.Anything, domain.Owner(), mustRepoName(t, "demo"), app.CreateMergeRequestParams{ | |
| @@ -54,12 +56,14 @@ func TestMergeRequestServiceCreate(t *testing.T) { | |||
| 54 | 56 | } | |
| 55 | 57 | ||
| 56 | 58 | func TestMergeRequestServiceListRejectsUnknownState(t *testing.T) { | |
| 59 | + | t.Parallel() | |
| 57 | 60 | _, err := (&mergeRequestService{mocks.NewMergeRequests(t)}).ListMergeRequests(ownerCtx(), | |
| 58 | 61 | connect.NewRequest(&pb.ListMergeRequestsRequest{Repo: "demo", State: pb.MergeRequestState(99)})) | |
| 59 | 62 | requireCode(t, connect.CodeInvalidArgument, err) | |
| 60 | 63 | } | |
| 61 | 64 | ||
| 62 | 65 | func TestMergeRequestServiceListFiltersByState(t *testing.T) { | |
| 66 | + | t.Parallel() | |
| 63 | 67 | mrs := mocks.NewMergeRequests(t) | |
| 64 | 68 | mrs.EXPECT().List(mock.Anything, domain.Owner(), mustRepoName(t, "demo"), app.ListMergeRequestsParams{ | |
| 65 | 69 | State: domain.MRStateMerged, PageSize: 3, PageToken: "tok", | |
| @@ -73,6 +77,7 @@ func TestMergeRequestServiceListFiltersByState(t *testing.T) { | |||
| 73 | 77 | } | |
| 74 | 78 | ||
| 75 | 79 | func TestMergeRequestServiceAddComment(t *testing.T) { | |
| 80 | + | t.Parallel() | |
| 76 | 81 | tests := []struct { | |
| 77 | 82 | name string | |
| 78 | 83 | side pb.DiffSide | |
| @@ -88,6 +93,7 @@ func TestMergeRequestServiceAddComment(t *testing.T) { | |||
| 88 | 93 | } | |
| 89 | 94 | for _, tt := range tests { | |
| 90 | 95 | t.Run(tt.name, func(t *testing.T) { | |
| 96 | + | t.Parallel() | |
| 91 | 97 | mrs := mocks.NewMergeRequests(t) | |
| 92 | 98 | if tt.wantCall { | |
| 93 | 99 | mrs.EXPECT().AddComment(mock.Anything, domain.Owner(), mustRepoName(t, "demo"), int64(2), mock.Anything). | |
| @@ -107,8 +113,10 @@ func TestMergeRequestServiceAddComment(t *testing.T) { | |||
| 107 | 113 | } | |
| 108 | 114 | ||
| 109 | 115 | func TestMergeRequestServiceResolveCommentMapsUnissuedIDsToZero(t *testing.T) { | |
| 116 | + | t.Parallel() | |
| 110 | 117 | for _, id := range []string{"", "abc", "-1", "0", "007", "99999999999999999999"} { | |
| 111 | 118 | t.Run(id, func(t *testing.T) { | |
| 119 | + | t.Parallel() | |
| 112 | 120 | mrs := mocks.NewMergeRequests(t) | |
| 113 | 121 | mrs.EXPECT().ResolveComment(mock.Anything, domain.Owner(), mustRepoName(t, "demo"), int64(1), int64(0), true). | |
| 114 | 122 | Return(domain.Comment{}, domain.ErrNotFound).Once() | |
| @@ -121,6 +129,7 @@ func TestMergeRequestServiceResolveCommentMapsUnissuedIDsToZero(t *testing.T) { | |||
| 121 | 129 | } | |
| 122 | 130 | ||
| 123 | 131 | func TestMergeRequestServiceMerge(t *testing.T) { | |
| 132 | + | t.Parallel() | |
| 124 | 133 | const head = "0123456789abcdef0123456789abcdef01234567" | |
| 125 | 134 | tests := []struct { | |
| 126 | 135 | name string | |
| @@ -138,6 +147,7 @@ func TestMergeRequestServiceMerge(t *testing.T) { | |||
| 138 | 147 | } | |
| 139 | 148 | for _, tt := range tests { | |
| 140 | 149 | t.Run(tt.name, func(t *testing.T) { | |
| 150 | + | t.Parallel() | |
| 141 | 151 | mrs := mocks.NewMergeRequests(t) | |
| 142 | 152 | if tt.wantCall { | |
| 143 | 153 | mrs.EXPECT().Merge(mock.Anything, domain.Owner(), mustRepoName(t, "demo"), int64(1), mock.MatchedBy(func(p app.MergeParams) bool { | |
| @@ -156,13 +166,16 @@ func TestMergeRequestServiceMerge(t *testing.T) { | |||
| 156 | 166 | } | |
| 157 | 167 | ||
| 158 | 168 | func TestMergeRequestServiceCloseAndMarkRead(t *testing.T) { | |
| 169 | + | t.Parallel() | |
| 159 | 170 | t.Run("close not found", func(t *testing.T) { | |
| 171 | + | t.Parallel() | |
| 160 | 172 | mrs := mocks.NewMergeRequests(t) | |
| 161 | 173 | mrs.EXPECT().Close(mock.Anything, domain.Owner(), mustRepoName(t, "demo"), int64(9)).Return(domain.MergeRequest{}, domain.ErrNotFound).Once() | |
| 162 | 174 | _, err := (&mergeRequestService{mrs}).CloseMergeRequest(ownerCtx(), connect.NewRequest(&pb.CloseMergeRequestRequest{Repo: "demo", Number: 9})) | |
| 163 | 175 | requireCode(t, connect.CodeNotFound, err) | |
| 164 | 176 | }) | |
| 165 | 177 | t.Run("close ok", func(t *testing.T) { | |
| 178 | + | t.Parallel() | |
| 166 | 179 | mrs := mocks.NewMergeRequests(t) | |
| 167 | 180 | mrs.EXPECT().Close(mock.Anything, domain.Owner(), mustRepoName(t, "demo"), int64(9)).Return(domain.MergeRequest{Number: 9, State: domain.MRStateClosed}, nil).Once() | |
| 168 | 181 | resp, err := (&mergeRequestService{mrs}).CloseMergeRequest(ownerCtx(), connect.NewRequest(&pb.CloseMergeRequestRequest{Repo: "demo", Number: 9})) | |
| @@ -170,11 +183,13 @@ func TestMergeRequestServiceCloseAndMarkRead(t *testing.T) { | |||
| 170 | 183 | assert.Equal(t, pb.MergeRequestState_MERGE_REQUEST_STATE_CLOSED, resp.Msg.GetMergeRequest().GetState()) | |
| 171 | 184 | }) | |
| 172 | 185 | t.Run("mark read rejects a short head", func(t *testing.T) { | |
| 186 | + | t.Parallel() | |
| 173 | 187 | _, err := (&mergeRequestService{mocks.NewMergeRequests(t)}).MarkRead(ownerCtx(), | |
| 174 | 188 | connect.NewRequest(&pb.MarkReadRequest{Repo: "demo", Number: 1, HeadId: "abc"})) | |
| 175 | 189 | requireCode(t, connect.CodeInvalidArgument, err) | |
| 176 | 190 | }) | |
| 177 | 191 | t.Run("mark read unauthenticated", func(t *testing.T) { | |
| 192 | + | t.Parallel() | |
| 178 | 193 | mrs := mocks.NewMergeRequests(t) | |
| 179 | 194 | mrs.EXPECT().MarkRead(mock.Anything, domain.Owner(), mustRepoName(t, "demo"), int64(1), domain.ObjectID{}).Return(domain.ObjectID{}, domain.ErrUnauthenticated).Once() | |
| 180 | 195 | _, err := (&mergeRequestService{mrs}).MarkRead(ownerCtx(), connect.NewRequest(&pb.MarkReadRequest{Repo: "demo", Number: 1})) | |
| @@ -183,6 +198,7 @@ func TestMergeRequestServiceCloseAndMarkRead(t *testing.T) { | |||
| 183 | 198 | } | |
| 184 | 199 | ||
| 185 | 200 | func TestMergeRequestServiceGet(t *testing.T) { | |
| 201 | + | t.Parallel() | |
| 186 | 202 | tests := []struct { | |
| 187 | 203 | name string | |
| 188 | 204 | usecase error | |
| @@ -194,6 +210,7 @@ func TestMergeRequestServiceGet(t *testing.T) { | |||
| 194 | 210 | } | |
| 195 | 211 | for _, tt := range tests { | |
| 196 | 212 | t.Run(tt.name, func(t *testing.T) { | |
| 213 | + | t.Parallel() | |
| 197 | 214 | mrs := mocks.NewMergeRequests(t) | |
| 198 | 215 | mrs.EXPECT().Get(mock.Anything, domain.Owner(), mustRepoName(t, "demo"), int64(4), true). | |
| 199 | 216 | Return(app.MergeRequestDetail{MergeRequest: domain.MergeRequest{Number: 4}, CommitsTruncated: true}, tt.usecase).Once() | |
| @@ -17,6 +17,7 @@ import ( | |||||
| 17 | ) | 17 | ) | ||
| 18 | 18 | ||||
| 19 | func TestMergeRequestServiceCreate(t *testing.T) { | 19 | func TestMergeRequestServiceCreate(t *testing.T) { | ||
| 20 | + | t.Parallel() | |||
| 20 | tests := []struct { | 21 | tests := []struct { | ||
| 21 | name string | 22 | name string | ||
| 22 | repo string | 23 | repo string | ||
| @@ -33,6 +34,7 @@ func TestMergeRequestServiceCreate(t *testing.T) { | |||||
| 33 | } | 34 | } | ||
| 34 | for _, tt := range tests { | 35 | for _, tt := range tests { | ||
| 35 | t.Run(tt.name, func(t *testing.T) { | 36 | t.Run(tt.name, func(t *testing.T) { | ||
| 37 | + | t.Parallel() | |||
| 36 | mrs := mocks.NewMergeRequests(t) | 38 | mrs := mocks.NewMergeRequests(t) | ||
| 37 | if tt.wantCall { | 39 | if tt.wantCall { | ||
| 38 | mrs.EXPECT().Create(mock.Anything, domain.Owner(), mustRepoName(t, "demo"), app.CreateMergeRequestParams{ | 40 | mrs.EXPECT().Create(mock.Anything, domain.Owner(), mustRepoName(t, "demo"), app.CreateMergeRequestParams{ | ||
| @@ -54,12 +56,14 @@ func TestMergeRequestServiceCreate(t *testing.T) { | |||||
| 54 | } | 56 | } | ||
| 55 | 57 | ||||
| 56 | func TestMergeRequestServiceListRejectsUnknownState(t *testing.T) { | 58 | func TestMergeRequestServiceListRejectsUnknownState(t *testing.T) { | ||
| 59 | + | t.Parallel() | |||
| 57 | _, err := (&mergeRequestService{mocks.NewMergeRequests(t)}).ListMergeRequests(ownerCtx(), | 60 | _, err := (&mergeRequestService{mocks.NewMergeRequests(t)}).ListMergeRequests(ownerCtx(), | ||
| 58 | connect.NewRequest(&pb.ListMergeRequestsRequest{Repo: "demo", State: pb.MergeRequestState(99)})) | 61 | connect.NewRequest(&pb.ListMergeRequestsRequest{Repo: "demo", State: pb.MergeRequestState(99)})) | ||
| 59 | requireCode(t, connect.CodeInvalidArgument, err) | 62 | requireCode(t, connect.CodeInvalidArgument, err) | ||
| 60 | } | 63 | } | ||
| 61 | 64 | ||||
| 62 | func TestMergeRequestServiceListFiltersByState(t *testing.T) { | 65 | func TestMergeRequestServiceListFiltersByState(t *testing.T) { | ||
| 66 | + | t.Parallel() | |||
| 63 | mrs := mocks.NewMergeRequests(t) | 67 | mrs := mocks.NewMergeRequests(t) | ||
| 64 | mrs.EXPECT().List(mock.Anything, domain.Owner(), mustRepoName(t, "demo"), app.ListMergeRequestsParams{ | 68 | mrs.EXPECT().List(mock.Anything, domain.Owner(), mustRepoName(t, "demo"), app.ListMergeRequestsParams{ | ||
| 65 | State: domain.MRStateMerged, PageSize: 3, PageToken: "tok", | 69 | State: domain.MRStateMerged, PageSize: 3, PageToken: "tok", | ||
| @@ -73,6 +77,7 @@ func TestMergeRequestServiceListFiltersByState(t *testing.T) { | |||||
| 73 | } | 77 | } | ||
| 74 | 78 | ||||
| 75 | func TestMergeRequestServiceAddComment(t *testing.T) { | 79 | func TestMergeRequestServiceAddComment(t *testing.T) { | ||
| 80 | + | t.Parallel() | |||
| 76 | tests := []struct { | 81 | tests := []struct { | ||
| 77 | name string | 82 | name string | ||
| 78 | side pb.DiffSide | 83 | side pb.DiffSide | ||
| @@ -88,6 +93,7 @@ func TestMergeRequestServiceAddComment(t *testing.T) { | |||||
| 88 | } | 93 | } | ||
| 89 | for _, tt := range tests { | 94 | for _, tt := range tests { | ||
| 90 | t.Run(tt.name, func(t *testing.T) { | 95 | t.Run(tt.name, func(t *testing.T) { | ||
| 96 | + | t.Parallel() | |||
| 91 | mrs := mocks.NewMergeRequests(t) | 97 | mrs := mocks.NewMergeRequests(t) | ||
| 92 | if tt.wantCall { | 98 | if tt.wantCall { | ||
| 93 | mrs.EXPECT().AddComment(mock.Anything, domain.Owner(), mustRepoName(t, "demo"), int64(2), mock.Anything). | 99 | mrs.EXPECT().AddComment(mock.Anything, domain.Owner(), mustRepoName(t, "demo"), int64(2), mock.Anything). | ||
| @@ -107,8 +113,10 @@ func TestMergeRequestServiceAddComment(t *testing.T) { | |||||
| 107 | } | 113 | } | ||
| 108 | 114 | ||||
| 109 | func TestMergeRequestServiceResolveCommentMapsUnissuedIDsToZero(t *testing.T) { | 115 | func TestMergeRequestServiceResolveCommentMapsUnissuedIDsToZero(t *testing.T) { | ||
| 116 | + | t.Parallel() | |||
| 110 | for _, id := range []string{"", "abc", "-1", "0", "007", "99999999999999999999"} { | 117 | for _, id := range []string{"", "abc", "-1", "0", "007", "99999999999999999999"} { | ||
| 111 | t.Run(id, func(t *testing.T) { | 118 | t.Run(id, func(t *testing.T) { | ||
| 119 | + | t.Parallel() | |||
| 112 | mrs := mocks.NewMergeRequests(t) | 120 | mrs := mocks.NewMergeRequests(t) | ||
| 113 | mrs.EXPECT().ResolveComment(mock.Anything, domain.Owner(), mustRepoName(t, "demo"), int64(1), int64(0), true). | 121 | mrs.EXPECT().ResolveComment(mock.Anything, domain.Owner(), mustRepoName(t, "demo"), int64(1), int64(0), true). | ||
| 114 | Return(domain.Comment{}, domain.ErrNotFound).Once() | 122 | Return(domain.Comment{}, domain.ErrNotFound).Once() | ||
| @@ -121,6 +129,7 @@ func TestMergeRequestServiceResolveCommentMapsUnissuedIDsToZero(t *testing.T) { | |||||
| 121 | } | 129 | } | ||
| 122 | 130 | ||||
| 123 | func TestMergeRequestServiceMerge(t *testing.T) { | 131 | func TestMergeRequestServiceMerge(t *testing.T) { | ||
| 132 | + | t.Parallel() | |||
| 124 | const head = "0123456789abcdef0123456789abcdef01234567" | 133 | const head = "0123456789abcdef0123456789abcdef01234567" | ||
| 125 | tests := []struct { | 134 | tests := []struct { | ||
| 126 | name string | 135 | name string | ||
| @@ -138,6 +147,7 @@ func TestMergeRequestServiceMerge(t *testing.T) { | |||||
| 138 | } | 147 | } | ||
| 139 | for _, tt := range tests { | 148 | for _, tt := range tests { | ||
| 140 | t.Run(tt.name, func(t *testing.T) { | 149 | t.Run(tt.name, func(t *testing.T) { | ||
| 150 | + | t.Parallel() | |||
| 141 | mrs := mocks.NewMergeRequests(t) | 151 | mrs := mocks.NewMergeRequests(t) | ||
| 142 | if tt.wantCall { | 152 | if tt.wantCall { | ||
| 143 | mrs.EXPECT().Merge(mock.Anything, domain.Owner(), mustRepoName(t, "demo"), int64(1), mock.MatchedBy(func(p app.MergeParams) bool { | 153 | mrs.EXPECT().Merge(mock.Anything, domain.Owner(), mustRepoName(t, "demo"), int64(1), mock.MatchedBy(func(p app.MergeParams) bool { | ||
| @@ -156,13 +166,16 @@ func TestMergeRequestServiceMerge(t *testing.T) { | |||||
| 156 | } | 166 | } | ||
| 157 | 167 | ||||
| 158 | func TestMergeRequestServiceCloseAndMarkRead(t *testing.T) { | 168 | func TestMergeRequestServiceCloseAndMarkRead(t *testing.T) { | ||
| 169 | + | t.Parallel() | |||
| 159 | t.Run("close not found", func(t *testing.T) { | 170 | t.Run("close not found", func(t *testing.T) { | ||
| 171 | + | t.Parallel() | |||
| 160 | mrs := mocks.NewMergeRequests(t) | 172 | mrs := mocks.NewMergeRequests(t) | ||
| 161 | mrs.EXPECT().Close(mock.Anything, domain.Owner(), mustRepoName(t, "demo"), int64(9)).Return(domain.MergeRequest{}, domain.ErrNotFound).Once() | 173 | mrs.EXPECT().Close(mock.Anything, domain.Owner(), mustRepoName(t, "demo"), int64(9)).Return(domain.MergeRequest{}, domain.ErrNotFound).Once() | ||
| 162 | _, err := (&mergeRequestService{mrs}).CloseMergeRequest(ownerCtx(), connect.NewRequest(&pb.CloseMergeRequestRequest{Repo: "demo", Number: 9})) | 174 | _, err := (&mergeRequestService{mrs}).CloseMergeRequest(ownerCtx(), connect.NewRequest(&pb.CloseMergeRequestRequest{Repo: "demo", Number: 9})) | ||
| 163 | requireCode(t, connect.CodeNotFound, err) | 175 | requireCode(t, connect.CodeNotFound, err) | ||
| 164 | }) | 176 | }) | ||
| 165 | t.Run("close ok", func(t *testing.T) { | 177 | t.Run("close ok", func(t *testing.T) { | ||
| 178 | + | t.Parallel() | |||
| 166 | mrs := mocks.NewMergeRequests(t) | 179 | mrs := mocks.NewMergeRequests(t) | ||
| 167 | mrs.EXPECT().Close(mock.Anything, domain.Owner(), mustRepoName(t, "demo"), int64(9)).Return(domain.MergeRequest{Number: 9, State: domain.MRStateClosed}, nil).Once() | 180 | mrs.EXPECT().Close(mock.Anything, domain.Owner(), mustRepoName(t, "demo"), int64(9)).Return(domain.MergeRequest{Number: 9, State: domain.MRStateClosed}, nil).Once() | ||
| 168 | resp, err := (&mergeRequestService{mrs}).CloseMergeRequest(ownerCtx(), connect.NewRequest(&pb.CloseMergeRequestRequest{Repo: "demo", Number: 9})) | 181 | resp, err := (&mergeRequestService{mrs}).CloseMergeRequest(ownerCtx(), connect.NewRequest(&pb.CloseMergeRequestRequest{Repo: "demo", Number: 9})) | ||
| @@ -170,11 +183,13 @@ func TestMergeRequestServiceCloseAndMarkRead(t *testing.T) { | |||||
| 170 | assert.Equal(t, pb.MergeRequestState_MERGE_REQUEST_STATE_CLOSED, resp.Msg.GetMergeRequest().GetState()) | 183 | assert.Equal(t, pb.MergeRequestState_MERGE_REQUEST_STATE_CLOSED, resp.Msg.GetMergeRequest().GetState()) | ||
| 171 | }) | 184 | }) | ||
| 172 | t.Run("mark read rejects a short head", func(t *testing.T) { | 185 | t.Run("mark read rejects a short head", func(t *testing.T) { | ||
| 186 | + | t.Parallel() | |||
| 173 | _, err := (&mergeRequestService{mocks.NewMergeRequests(t)}).MarkRead(ownerCtx(), | 187 | _, err := (&mergeRequestService{mocks.NewMergeRequests(t)}).MarkRead(ownerCtx(), | ||
| 174 | connect.NewRequest(&pb.MarkReadRequest{Repo: "demo", Number: 1, HeadId: "abc"})) | 188 | connect.NewRequest(&pb.MarkReadRequest{Repo: "demo", Number: 1, HeadId: "abc"})) | ||
| 175 | requireCode(t, connect.CodeInvalidArgument, err) | 189 | requireCode(t, connect.CodeInvalidArgument, err) | ||
| 176 | }) | 190 | }) | ||
| 177 | t.Run("mark read unauthenticated", func(t *testing.T) { | 191 | t.Run("mark read unauthenticated", func(t *testing.T) { | ||
| 192 | + | t.Parallel() | |||
| 178 | mrs := mocks.NewMergeRequests(t) | 193 | mrs := mocks.NewMergeRequests(t) | ||
| 179 | mrs.EXPECT().MarkRead(mock.Anything, domain.Owner(), mustRepoName(t, "demo"), int64(1), domain.ObjectID{}).Return(domain.ObjectID{}, domain.ErrUnauthenticated).Once() | 194 | mrs.EXPECT().MarkRead(mock.Anything, domain.Owner(), mustRepoName(t, "demo"), int64(1), domain.ObjectID{}).Return(domain.ObjectID{}, domain.ErrUnauthenticated).Once() | ||
| 180 | _, err := (&mergeRequestService{mrs}).MarkRead(ownerCtx(), connect.NewRequest(&pb.MarkReadRequest{Repo: "demo", Number: 1})) | 195 | _, err := (&mergeRequestService{mrs}).MarkRead(ownerCtx(), connect.NewRequest(&pb.MarkReadRequest{Repo: "demo", Number: 1})) | ||
| @@ -183,6 +198,7 @@ func TestMergeRequestServiceCloseAndMarkRead(t *testing.T) { | |||||
| 183 | } | 198 | } | ||
| 184 | 199 | ||||
| 185 | func TestMergeRequestServiceGet(t *testing.T) { | 200 | func TestMergeRequestServiceGet(t *testing.T) { | ||
| 201 | + | t.Parallel() | |||
| 186 | tests := []struct { | 202 | tests := []struct { | ||
| 187 | name string | 203 | name string | ||
| 188 | usecase error | 204 | usecase error | ||
| @@ -194,6 +210,7 @@ func TestMergeRequestServiceGet(t *testing.T) { | |||||
| 194 | } | 210 | } | ||
| 195 | for _, tt := range tests { | 211 | for _, tt := range tests { | ||
| 196 | t.Run(tt.name, func(t *testing.T) { | 212 | t.Run(tt.name, func(t *testing.T) { | ||
| 213 | + | t.Parallel() | |||
| 197 | mrs := mocks.NewMergeRequests(t) | 214 | mrs := mocks.NewMergeRequests(t) | ||
| 198 | mrs.EXPECT().Get(mock.Anything, domain.Owner(), mustRepoName(t, "demo"), int64(4), true). | 215 | mrs.EXPECT().Get(mock.Anything, domain.Owner(), mustRepoName(t, "demo"), int64(4), true). | ||
| 199 | Return(app.MergeRequestDetail{MergeRequest: domain.MergeRequest{Number: 4}, CommitsTruncated: true}, tt.usecase).Once() | 216 | Return(app.MergeRequestDetail{MergeRequest: domain.MergeRequest{Number: 4}, CommitsTruncated: true}, tt.usecase).Once() | ||
M internal/adapters/rpc/repo_test.go +11 -0
| @@ -39,6 +39,7 @@ func requireCode(t *testing.T, want connect.Code, err error) { | |||
| 39 | 39 | } | |
| 40 | 40 | ||
| 41 | 41 | func TestRepoServiceGetRepo(t *testing.T) { | |
| 42 | + | t.Parallel() | |
| 42 | 43 | boom := errors.New("disk on fire") | |
| 43 | 44 | tests := []struct { | |
| 44 | 45 | name string | |
| @@ -58,6 +59,7 @@ func TestRepoServiceGetRepo(t *testing.T) { | |||
| 58 | 59 | } | |
| 59 | 60 | for _, tt := range tests { | |
| 60 | 61 | t.Run(tt.name, func(t *testing.T) { | |
| 62 | + | t.Parallel() | |
| 61 | 63 | repos := mocks.NewRepos(t) | |
| 62 | 64 | if tt.wantCall { | |
| 63 | 65 | repo := domain.Repo{Name: mustRepoName(t, "demo"), Description: "d", Visibility: domain.VisibilityPublic} | |
| @@ -83,6 +85,7 @@ func TestRepoServiceGetRepo(t *testing.T) { | |||
| 83 | 85 | } | |
| 84 | 86 | ||
| 85 | 87 | func TestRepoServiceCreateRepo(t *testing.T) { | |
| 88 | + | t.Parallel() | |
| 86 | 89 | tests := []struct { | |
| 87 | 90 | name string | |
| 88 | 91 | req *pb.CreateRepoRequest | |
| @@ -98,6 +101,7 @@ func TestRepoServiceCreateRepo(t *testing.T) { | |||
| 98 | 101 | } | |
| 99 | 102 | for _, tt := range tests { | |
| 100 | 103 | t.Run(tt.name, func(t *testing.T) { | |
| 104 | + | t.Parallel() | |
| 101 | 105 | repos := mocks.NewRepos(t) | |
| 102 | 106 | if tt.wantCall { | |
| 103 | 107 | repo := domain.Repo{Name: mustRepoName(t, "demo"), Visibility: domain.VisibilityPrivate} | |
| @@ -121,6 +125,7 @@ func TestRepoServiceCreateRepo(t *testing.T) { | |||
| 121 | 125 | } | |
| 122 | 126 | ||
| 123 | 127 | func TestRepoServiceUpdateRepoValidatesMask(t *testing.T) { | |
| 128 | + | t.Parallel() | |
| 124 | 129 | tests := []struct { | |
| 125 | 130 | name string | |
| 126 | 131 | req *pb.UpdateRepoRequest | |
| @@ -164,6 +169,7 @@ func TestRepoServiceUpdateRepoValidatesMask(t *testing.T) { | |||
| 164 | 169 | } | |
| 165 | 170 | for _, tt := range tests { | |
| 166 | 171 | t.Run(tt.name, func(t *testing.T) { | |
| 172 | + | t.Parallel() | |
| 167 | 173 | repos := mocks.NewRepos(t) | |
| 168 | 174 | if tt.wantCall { | |
| 169 | 175 | repo := domain.Repo{Name: mustRepoName(t, "demo"), Description: "new"} | |
| @@ -184,6 +190,7 @@ func TestRepoServiceUpdateRepoValidatesMask(t *testing.T) { | |||
| 184 | 190 | } | |
| 185 | 191 | ||
| 186 | 192 | func TestRepoServiceDeleteRepo(t *testing.T) { | |
| 193 | + | t.Parallel() | |
| 187 | 194 | tests := []struct { | |
| 188 | 195 | name string | |
| 189 | 196 | repo string | |
| @@ -198,6 +205,7 @@ func TestRepoServiceDeleteRepo(t *testing.T) { | |||
| 198 | 205 | } | |
| 199 | 206 | for _, tt := range tests { | |
| 200 | 207 | t.Run(tt.name, func(t *testing.T) { | |
| 208 | + | t.Parallel() | |
| 201 | 209 | repos := mocks.NewRepos(t) | |
| 202 | 210 | if tt.wantCall { | |
| 203 | 211 | repos.EXPECT().Delete(mock.Anything, domain.Owner(), mustRepoName(t, tt.repo)).Return(tt.usecase).Once() | |
| @@ -213,7 +221,9 @@ func TestRepoServiceDeleteRepo(t *testing.T) { | |||
| 213 | 221 | } | |
| 214 | 222 | ||
| 215 | 223 | func TestRepoServiceListRepos(t *testing.T) { | |
| 224 | + | t.Parallel() | |
| 216 | 225 | t.Run("pages and views every repo", func(t *testing.T) { | |
| 226 | + | t.Parallel() | |
| 217 | 227 | repos := mocks.NewRepos(t) | |
| 218 | 228 | a := domain.Repo{Name: mustRepoName(t, "a")} | |
| 219 | 229 | b := domain.Repo{Name: mustRepoName(t, "b")} | |
| @@ -234,6 +244,7 @@ func TestRepoServiceListRepos(t *testing.T) { | |||
| 234 | 244 | "unauthenticated": {domain.ErrUnauthenticated, connect.CodeUnauthenticated}, | |
| 235 | 245 | } { | |
| 236 | 246 | t.Run(name, func(t *testing.T) { | |
| 247 | + | t.Parallel() | |
| 237 | 248 | repos := mocks.NewRepos(t) | |
| 238 | 249 | repos.EXPECT().ListPage(mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(app.RepoPage{}, tc.err).Once() | |
| 239 | 250 | _, err := (&repoService{repos}).ListRepos(ownerCtx(), connect.NewRequest(&pb.ListReposRequest{})) | |
| @@ -39,6 +39,7 @@ func requireCode(t *testing.T, want connect.Code, err error) { | |||||
| 39 | } | 39 | } | ||
| 40 | 40 | ||||
| 41 | func TestRepoServiceGetRepo(t *testing.T) { | 41 | func TestRepoServiceGetRepo(t *testing.T) { | ||
| 42 | + | t.Parallel() | |||
| 42 | boom := errors.New("disk on fire") | 43 | boom := errors.New("disk on fire") | ||
| 43 | tests := []struct { | 44 | tests := []struct { | ||
| 44 | name string | 45 | name string | ||
| @@ -58,6 +59,7 @@ func TestRepoServiceGetRepo(t *testing.T) { | |||||
| 58 | } | 59 | } | ||
| 59 | for _, tt := range tests { | 60 | for _, tt := range tests { | ||
| 60 | t.Run(tt.name, func(t *testing.T) { | 61 | t.Run(tt.name, func(t *testing.T) { | ||
| 62 | + | t.Parallel() | |||
| 61 | repos := mocks.NewRepos(t) | 63 | repos := mocks.NewRepos(t) | ||
| 62 | if tt.wantCall { | 64 | if tt.wantCall { | ||
| 63 | repo := domain.Repo{Name: mustRepoName(t, "demo"), Description: "d", Visibility: domain.VisibilityPublic} | 65 | repo := domain.Repo{Name: mustRepoName(t, "demo"), Description: "d", Visibility: domain.VisibilityPublic} | ||
| @@ -83,6 +85,7 @@ func TestRepoServiceGetRepo(t *testing.T) { | |||||
| 83 | } | 85 | } | ||
| 84 | 86 | ||||
| 85 | func TestRepoServiceCreateRepo(t *testing.T) { | 87 | func TestRepoServiceCreateRepo(t *testing.T) { | ||
| 88 | + | t.Parallel() | |||
| 86 | tests := []struct { | 89 | tests := []struct { | ||
| 87 | name string | 90 | name string | ||
| 88 | req *pb.CreateRepoRequest | 91 | req *pb.CreateRepoRequest | ||
| @@ -98,6 +101,7 @@ func TestRepoServiceCreateRepo(t *testing.T) { | |||||
| 98 | } | 101 | } | ||
| 99 | for _, tt := range tests { | 102 | for _, tt := range tests { | ||
| 100 | t.Run(tt.name, func(t *testing.T) { | 103 | t.Run(tt.name, func(t *testing.T) { | ||
| 104 | + | t.Parallel() | |||
| 101 | repos := mocks.NewRepos(t) | 105 | repos := mocks.NewRepos(t) | ||
| 102 | if tt.wantCall { | 106 | if tt.wantCall { | ||
| 103 | repo := domain.Repo{Name: mustRepoName(t, "demo"), Visibility: domain.VisibilityPrivate} | 107 | repo := domain.Repo{Name: mustRepoName(t, "demo"), Visibility: domain.VisibilityPrivate} | ||
| @@ -121,6 +125,7 @@ func TestRepoServiceCreateRepo(t *testing.T) { | |||||
| 121 | } | 125 | } | ||
| 122 | 126 | ||||
| 123 | func TestRepoServiceUpdateRepoValidatesMask(t *testing.T) { | 127 | func TestRepoServiceUpdateRepoValidatesMask(t *testing.T) { | ||
| 128 | + | t.Parallel() | |||
| 124 | tests := []struct { | 129 | tests := []struct { | ||
| 125 | name string | 130 | name string | ||
| 126 | req *pb.UpdateRepoRequest | 131 | req *pb.UpdateRepoRequest | ||
| @@ -164,6 +169,7 @@ func TestRepoServiceUpdateRepoValidatesMask(t *testing.T) { | |||||
| 164 | } | 169 | } | ||
| 165 | for _, tt := range tests { | 170 | for _, tt := range tests { | ||
| 166 | t.Run(tt.name, func(t *testing.T) { | 171 | t.Run(tt.name, func(t *testing.T) { | ||
| 172 | + | t.Parallel() | |||
| 167 | repos := mocks.NewRepos(t) | 173 | repos := mocks.NewRepos(t) | ||
| 168 | if tt.wantCall { | 174 | if tt.wantCall { | ||
| 169 | repo := domain.Repo{Name: mustRepoName(t, "demo"), Description: "new"} | 175 | repo := domain.Repo{Name: mustRepoName(t, "demo"), Description: "new"} | ||
| @@ -184,6 +190,7 @@ func TestRepoServiceUpdateRepoValidatesMask(t *testing.T) { | |||||
| 184 | } | 190 | } | ||
| 185 | 191 | ||||
| 186 | func TestRepoServiceDeleteRepo(t *testing.T) { | 192 | func TestRepoServiceDeleteRepo(t *testing.T) { | ||
| 193 | + | t.Parallel() | |||
| 187 | tests := []struct { | 194 | tests := []struct { | ||
| 188 | name string | 195 | name string | ||
| 189 | repo string | 196 | repo string | ||
| @@ -198,6 +205,7 @@ func TestRepoServiceDeleteRepo(t *testing.T) { | |||||
| 198 | } | 205 | } | ||
| 199 | for _, tt := range tests { | 206 | for _, tt := range tests { | ||
| 200 | t.Run(tt.name, func(t *testing.T) { | 207 | t.Run(tt.name, func(t *testing.T) { | ||
| 208 | + | t.Parallel() | |||
| 201 | repos := mocks.NewRepos(t) | 209 | repos := mocks.NewRepos(t) | ||
| 202 | if tt.wantCall { | 210 | if tt.wantCall { | ||
| 203 | repos.EXPECT().Delete(mock.Anything, domain.Owner(), mustRepoName(t, tt.repo)).Return(tt.usecase).Once() | 211 | repos.EXPECT().Delete(mock.Anything, domain.Owner(), mustRepoName(t, tt.repo)).Return(tt.usecase).Once() | ||
| @@ -213,7 +221,9 @@ func TestRepoServiceDeleteRepo(t *testing.T) { | |||||
| 213 | } | 221 | } | ||
| 214 | 222 | ||||
| 215 | func TestRepoServiceListRepos(t *testing.T) { | 223 | func TestRepoServiceListRepos(t *testing.T) { | ||
| 224 | + | t.Parallel() | |||
| 216 | t.Run("pages and views every repo", func(t *testing.T) { | 225 | t.Run("pages and views every repo", func(t *testing.T) { | ||
| 226 | + | t.Parallel() | |||
| 217 | repos := mocks.NewRepos(t) | 227 | repos := mocks.NewRepos(t) | ||
| 218 | a := domain.Repo{Name: mustRepoName(t, "a")} | 228 | a := domain.Repo{Name: mustRepoName(t, "a")} | ||
| 219 | b := domain.Repo{Name: mustRepoName(t, "b")} | 229 | b := domain.Repo{Name: mustRepoName(t, "b")} | ||
| @@ -234,6 +244,7 @@ func TestRepoServiceListRepos(t *testing.T) { | |||||
| 234 | "unauthenticated": {domain.ErrUnauthenticated, connect.CodeUnauthenticated}, | 244 | "unauthenticated": {domain.ErrUnauthenticated, connect.CodeUnauthenticated}, | ||
| 235 | } { | 245 | } { | ||
| 236 | t.Run(name, func(t *testing.T) { | 246 | t.Run(name, func(t *testing.T) { | ||
| 247 | + | t.Parallel() | |||
| 237 | repos := mocks.NewRepos(t) | 248 | repos := mocks.NewRepos(t) | ||
| 238 | repos.EXPECT().ListPage(mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(app.RepoPage{}, tc.err).Once() | 249 | repos.EXPECT().ListPage(mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(app.RepoPage{}, tc.err).Once() | ||
| 239 | _, err := (&repoService{repos}).ListRepos(ownerCtx(), connect.NewRequest(&pb.ListReposRequest{})) | 250 | _, err := (&repoService{repos}).ListRepos(ownerCtx(), connect.NewRequest(&pb.ListReposRequest{})) | ||
M internal/adapters/rpc/server_test.go +2 -0
| @@ -16,6 +16,7 @@ import ( | |||
| 16 | 16 | ) | |
| 17 | 17 | ||
| 18 | 18 | func TestServerServiceGetVersion(t *testing.T) { | |
| 19 | + | t.Parallel() | |
| 19 | 20 | svc := &serverService{version: "v1.2.3", key: mocks.NewSigningKey(t)} | |
| 20 | 21 | resp, err := svc.GetVersion(context.Background(), connect.NewRequest(&pb.GetVersionRequest{})) | |
| 21 | 22 | require.NoError(t, err) | |
| @@ -28,6 +29,7 @@ func TestServerServiceGetVersion(t *testing.T) { | |||
| 28 | 29 | } | |
| 29 | 30 | ||
| 30 | 31 | func TestServerServiceGetSigningKey(t *testing.T) { | |
| 32 | + | t.Parallel() | |
| 31 | 33 | key := mocks.NewSigningKey(t) | |
| 32 | 34 | key.EXPECT().Info().Return(domain.SigningKeyInfo{ | |
| 33 | 35 | Key: domain.SigningPublicKey{AuthorizedKey: "ssh-ed25519 AAAA", Fingerprint: "SHA256:xyz"}, | |
| @@ -16,6 +16,7 @@ import ( | |||||
| 16 | ) | 16 | ) | ||
| 17 | 17 | ||||
| 18 | func TestServerServiceGetVersion(t *testing.T) { | 18 | func TestServerServiceGetVersion(t *testing.T) { | ||
| 19 | + | t.Parallel() | |||
| 19 | svc := &serverService{version: "v1.2.3", key: mocks.NewSigningKey(t)} | 20 | svc := &serverService{version: "v1.2.3", key: mocks.NewSigningKey(t)} | ||
| 20 | resp, err := svc.GetVersion(context.Background(), connect.NewRequest(&pb.GetVersionRequest{})) | 21 | resp, err := svc.GetVersion(context.Background(), connect.NewRequest(&pb.GetVersionRequest{})) | ||
| 21 | require.NoError(t, err) | 22 | require.NoError(t, err) | ||
| @@ -28,6 +29,7 @@ func TestServerServiceGetVersion(t *testing.T) { | |||||
| 28 | } | 29 | } | ||
| 29 | 30 | ||||
| 30 | func TestServerServiceGetSigningKey(t *testing.T) { | 31 | func TestServerServiceGetSigningKey(t *testing.T) { | ||
| 32 | + | t.Parallel() | |||
| 31 | key := mocks.NewSigningKey(t) | 33 | key := mocks.NewSigningKey(t) | ||
| 32 | key.EXPECT().Info().Return(domain.SigningKeyInfo{ | 34 | key.EXPECT().Info().Return(domain.SigningKeyInfo{ | ||
| 33 | Key: domain.SigningPublicKey{AuthorizedKey: "ssh-ed25519 AAAA", Fingerprint: "SHA256:xyz"}, | 35 | Key: domain.SigningPublicKey{AuthorizedKey: "ssh-ed25519 AAAA", Fingerprint: "SHA256:xyz"}, | ||
M internal/adapters/rpc/session_test.go +4 -0
| @@ -18,6 +18,7 @@ import ( | |||
| 18 | 18 | ) | |
| 19 | 19 | ||
| 20 | 20 | func TestSessionServiceCreateLoginLink(t *testing.T) { | |
| 21 | + | t.Parallel() | |
| 21 | 22 | exp := time.Date(2030, 1, 2, 3, 4, 5, 0, time.UTC) | |
| 22 | 23 | tests := []struct { | |
| 23 | 24 | name string | |
| @@ -30,6 +31,7 @@ func TestSessionServiceCreateLoginLink(t *testing.T) { | |||
| 30 | 31 | } | |
| 31 | 32 | for _, tt := range tests { | |
| 32 | 33 | t.Run(tt.name, func(t *testing.T) { | |
| 34 | + | t.Parallel() | |
| 33 | 35 | sessions := mocks.NewSessions(t) | |
| 34 | 36 | sessions.EXPECT().CreateLoginLink(mock.Anything, domain.Owner()). | |
| 35 | 37 | Return(app.LoginLink{URL: "https://x/-/login/n", ExpiresAt: exp}, tt.usecase).Once() | |
| @@ -46,6 +48,7 @@ func TestSessionServiceCreateLoginLink(t *testing.T) { | |||
| 46 | 48 | } | |
| 47 | 49 | ||
| 48 | 50 | func TestSessionServiceSignOutEverywhere(t *testing.T) { | |
| 51 | + | t.Parallel() | |
| 49 | 52 | tests := []struct { | |
| 50 | 53 | name string | |
| 51 | 54 | usecase error | |
| @@ -56,6 +59,7 @@ func TestSessionServiceSignOutEverywhere(t *testing.T) { | |||
| 56 | 59 | } | |
| 57 | 60 | for _, tt := range tests { | |
| 58 | 61 | t.Run(tt.name, func(t *testing.T) { | |
| 62 | + | t.Parallel() | |
| 59 | 63 | sessions := mocks.NewSessions(t) | |
| 60 | 64 | sessions.EXPECT().SignOutEverywhere(mock.Anything, domain.Owner()).Return(tt.usecase).Once() | |
| 61 | 65 | _, err := (&sessionService{sessions}).SignOutEverywhere(ownerCtx(), connect.NewRequest(&pb.SignOutEverywhereRequest{})) | |
| @@ -18,6 +18,7 @@ import ( | |||||
| 18 | ) | 18 | ) | ||
| 19 | 19 | ||||
| 20 | func TestSessionServiceCreateLoginLink(t *testing.T) { | 20 | func TestSessionServiceCreateLoginLink(t *testing.T) { | ||
| 21 | + | t.Parallel() | |||
| 21 | exp := time.Date(2030, 1, 2, 3, 4, 5, 0, time.UTC) | 22 | exp := time.Date(2030, 1, 2, 3, 4, 5, 0, time.UTC) | ||
| 22 | tests := []struct { | 23 | tests := []struct { | ||
| 23 | name string | 24 | name string | ||
| @@ -30,6 +31,7 @@ func TestSessionServiceCreateLoginLink(t *testing.T) { | |||||
| 30 | } | 31 | } | ||
| 31 | for _, tt := range tests { | 32 | for _, tt := range tests { | ||
| 32 | t.Run(tt.name, func(t *testing.T) { | 33 | t.Run(tt.name, func(t *testing.T) { | ||
| 34 | + | t.Parallel() | |||
| 33 | sessions := mocks.NewSessions(t) | 35 | sessions := mocks.NewSessions(t) | ||
| 34 | sessions.EXPECT().CreateLoginLink(mock.Anything, domain.Owner()). | 36 | sessions.EXPECT().CreateLoginLink(mock.Anything, domain.Owner()). | ||
| 35 | Return(app.LoginLink{URL: "https://x/-/login/n", ExpiresAt: exp}, tt.usecase).Once() | 37 | Return(app.LoginLink{URL: "https://x/-/login/n", ExpiresAt: exp}, tt.usecase).Once() | ||
| @@ -46,6 +48,7 @@ func TestSessionServiceCreateLoginLink(t *testing.T) { | |||||
| 46 | } | 48 | } | ||
| 47 | 49 | ||||
| 48 | func TestSessionServiceSignOutEverywhere(t *testing.T) { | 50 | func TestSessionServiceSignOutEverywhere(t *testing.T) { | ||
| 51 | + | t.Parallel() | |||
| 49 | tests := []struct { | 52 | tests := []struct { | ||
| 50 | name string | 53 | name string | ||
| 51 | usecase error | 54 | usecase error | ||
| @@ -56,6 +59,7 @@ func TestSessionServiceSignOutEverywhere(t *testing.T) { | |||||
| 56 | } | 59 | } | ||
| 57 | for _, tt := range tests { | 60 | for _, tt := range tests { | ||
| 58 | t.Run(tt.name, func(t *testing.T) { | 61 | t.Run(tt.name, func(t *testing.T) { | ||
| 62 | + | t.Parallel() | |||
| 59 | sessions := mocks.NewSessions(t) | 63 | sessions := mocks.NewSessions(t) | ||
| 60 | sessions.EXPECT().SignOutEverywhere(mock.Anything, domain.Owner()).Return(tt.usecase).Once() | 64 | sessions.EXPECT().SignOutEverywhere(mock.Anything, domain.Owner()).Return(tt.usecase).Once() | ||
| 61 | _, err := (&sessionService{sessions}).SignOutEverywhere(ownerCtx(), connect.NewRequest(&pb.SignOutEverywhereRequest{})) | 65 | _, err := (&sessionService{sessions}).SignOutEverywhere(ownerCtx(), connect.NewRequest(&pb.SignOutEverywhereRequest{})) | ||
M internal/adapters/rpc/token_test.go +6 -0
| @@ -17,6 +17,7 @@ import ( | |||
| 17 | 17 | ) | |
| 18 | 18 | ||
| 19 | 19 | func TestTokenServiceCreateToken(t *testing.T) { | |
| 20 | + | t.Parallel() | |
| 20 | 21 | tests := []struct { | |
| 21 | 22 | name string | |
| 22 | 23 | req *pb.CreateTokenRequest | |
| @@ -33,6 +34,7 @@ func TestTokenServiceCreateToken(t *testing.T) { | |||
| 33 | 34 | } | |
| 34 | 35 | for _, tt := range tests { | |
| 35 | 36 | t.Run(tt.name, func(t *testing.T) { | |
| 37 | + | t.Parallel() | |
| 36 | 38 | tokens := mocks.NewTokens(t) | |
| 37 | 39 | if tt.wantCall { | |
| 38 | 40 | tokens.EXPECT().Create(mock.Anything, domain.Owner(), mock.Anything). | |
| @@ -51,6 +53,7 @@ func TestTokenServiceCreateToken(t *testing.T) { | |||
| 51 | 53 | } | |
| 52 | 54 | ||
| 53 | 55 | func TestTokenServiceCreateTokenPassesParams(t *testing.T) { | |
| 56 | + | t.Parallel() | |
| 54 | 57 | tokens := mocks.NewTokens(t) | |
| 55 | 58 | tokens.EXPECT().Create(mock.Anything, domain.Owner(), mock.MatchedBy(func(p app.CreateTokenParams) bool { | |
| 56 | 59 | return p.Label == "ci" && len(p.Scopes) == 1 && len(p.Repos) == 1 && p.Repos[0].String() == "demo" | |
| @@ -62,6 +65,7 @@ func TestTokenServiceCreateTokenPassesParams(t *testing.T) { | |||
| 62 | 65 | } | |
| 63 | 66 | ||
| 64 | 67 | func TestTokenServiceRevokeToken(t *testing.T) { | |
| 68 | + | t.Parallel() | |
| 65 | 69 | tests := []struct { | |
| 66 | 70 | name string | |
| 67 | 71 | usecase error | |
| @@ -73,6 +77,7 @@ func TestTokenServiceRevokeToken(t *testing.T) { | |||
| 73 | 77 | } | |
| 74 | 78 | for _, tt := range tests { | |
| 75 | 79 | t.Run(tt.name, func(t *testing.T) { | |
| 80 | + | t.Parallel() | |
| 76 | 81 | tokens := mocks.NewTokens(t) | |
| 77 | 82 | tokens.EXPECT().Revoke(mock.Anything, domain.Owner(), "t1").Return(tt.usecase).Once() | |
| 78 | 83 | _, err := (&tokenService{tokens}).RevokeToken(ownerCtx(), connect.NewRequest(&pb.RevokeTokenRequest{Id: "t1"})) | |
| @@ -86,6 +91,7 @@ func TestTokenServiceRevokeToken(t *testing.T) { | |||
| 86 | 91 | } | |
| 87 | 92 | ||
| 88 | 93 | func TestTokenServiceListTokens(t *testing.T) { | |
| 94 | + | t.Parallel() | |
| 89 | 95 | tokens := mocks.NewTokens(t) | |
| 90 | 96 | tokens.EXPECT().ListPage(mock.Anything, domain.Owner(), 5, "tok"). | |
| 91 | 97 | Return(app.TokenPage{Tokens: []domain.Token{{ID: "a"}, {ID: "b"}}, NextPageToken: "n"}, nil).Once() | |
| @@ -17,6 +17,7 @@ import ( | |||||
| 17 | ) | 17 | ) | ||
| 18 | 18 | ||||
| 19 | func TestTokenServiceCreateToken(t *testing.T) { | 19 | func TestTokenServiceCreateToken(t *testing.T) { | ||
| 20 | + | t.Parallel() | |||
| 20 | tests := []struct { | 21 | tests := []struct { | ||
| 21 | name string | 22 | name string | ||
| 22 | req *pb.CreateTokenRequest | 23 | req *pb.CreateTokenRequest | ||
| @@ -33,6 +34,7 @@ func TestTokenServiceCreateToken(t *testing.T) { | |||||
| 33 | } | 34 | } | ||
| 34 | for _, tt := range tests { | 35 | for _, tt := range tests { | ||
| 35 | t.Run(tt.name, func(t *testing.T) { | 36 | t.Run(tt.name, func(t *testing.T) { | ||
| 37 | + | t.Parallel() | |||
| 36 | tokens := mocks.NewTokens(t) | 38 | tokens := mocks.NewTokens(t) | ||
| 37 | if tt.wantCall { | 39 | if tt.wantCall { | ||
| 38 | tokens.EXPECT().Create(mock.Anything, domain.Owner(), mock.Anything). | 40 | tokens.EXPECT().Create(mock.Anything, domain.Owner(), mock.Anything). | ||
| @@ -51,6 +53,7 @@ func TestTokenServiceCreateToken(t *testing.T) { | |||||
| 51 | } | 53 | } | ||
| 52 | 54 | ||||
| 53 | func TestTokenServiceCreateTokenPassesParams(t *testing.T) { | 55 | func TestTokenServiceCreateTokenPassesParams(t *testing.T) { | ||
| 56 | + | t.Parallel() | |||
| 54 | tokens := mocks.NewTokens(t) | 57 | tokens := mocks.NewTokens(t) | ||
| 55 | tokens.EXPECT().Create(mock.Anything, domain.Owner(), mock.MatchedBy(func(p app.CreateTokenParams) bool { | 58 | tokens.EXPECT().Create(mock.Anything, domain.Owner(), mock.MatchedBy(func(p app.CreateTokenParams) bool { | ||
| 56 | return p.Label == "ci" && len(p.Scopes) == 1 && len(p.Repos) == 1 && p.Repos[0].String() == "demo" | 59 | return p.Label == "ci" && len(p.Scopes) == 1 && len(p.Repos) == 1 && p.Repos[0].String() == "demo" | ||
| @@ -62,6 +65,7 @@ func TestTokenServiceCreateTokenPassesParams(t *testing.T) { | |||||
| 62 | } | 65 | } | ||
| 63 | 66 | ||||
| 64 | func TestTokenServiceRevokeToken(t *testing.T) { | 67 | func TestTokenServiceRevokeToken(t *testing.T) { | ||
| 68 | + | t.Parallel() | |||
| 65 | tests := []struct { | 69 | tests := []struct { | ||
| 66 | name string | 70 | name string | ||
| 67 | usecase error | 71 | usecase error | ||
| @@ -73,6 +77,7 @@ func TestTokenServiceRevokeToken(t *testing.T) { | |||||
| 73 | } | 77 | } | ||
| 74 | for _, tt := range tests { | 78 | for _, tt := range tests { | ||
| 75 | t.Run(tt.name, func(t *testing.T) { | 79 | t.Run(tt.name, func(t *testing.T) { | ||
| 80 | + | t.Parallel() | |||
| 76 | tokens := mocks.NewTokens(t) | 81 | tokens := mocks.NewTokens(t) | ||
| 77 | tokens.EXPECT().Revoke(mock.Anything, domain.Owner(), "t1").Return(tt.usecase).Once() | 82 | tokens.EXPECT().Revoke(mock.Anything, domain.Owner(), "t1").Return(tt.usecase).Once() | ||
| 78 | _, err := (&tokenService{tokens}).RevokeToken(ownerCtx(), connect.NewRequest(&pb.RevokeTokenRequest{Id: "t1"})) | 83 | _, err := (&tokenService{tokens}).RevokeToken(ownerCtx(), connect.NewRequest(&pb.RevokeTokenRequest{Id: "t1"})) | ||
| @@ -86,6 +91,7 @@ func TestTokenServiceRevokeToken(t *testing.T) { | |||||
| 86 | } | 91 | } | ||
| 87 | 92 | ||||
| 88 | func TestTokenServiceListTokens(t *testing.T) { | 93 | func TestTokenServiceListTokens(t *testing.T) { | ||
| 94 | + | t.Parallel() | |||
| 89 | tokens := mocks.NewTokens(t) | 95 | tokens := mocks.NewTokens(t) | ||
| 90 | tokens.EXPECT().ListPage(mock.Anything, domain.Owner(), 5, "tok"). | 96 | tokens.EXPECT().ListPage(mock.Anything, domain.Owner(), 5, "tok"). | ||
| 91 | Return(app.TokenPage{Tokens: []domain.Token{{ID: "a"}, {ID: "b"}}, NextPageToken: "n"}, nil).Once() | 97 | Return(app.TokenPage{Tokens: []domain.Token{{ID: "a"}, {ID: "b"}}, NextPageToken: "n"}, nil).Once() | ||
M internal/adapters/web/commits_test.go +2 -0
| @@ -15,6 +15,7 @@ import ( | |||
| 15 | 15 | ) | |
| 16 | 16 | ||
| 17 | 17 | func TestIsCommitID(t *testing.T) { | |
| 18 | + | t.Parallel() | |
| 18 | 19 | for _, s := range []string{"abcdef0", "0123456789abcdef0123456789abcdef01234567"} { | |
| 19 | 20 | assert.True(t, isCommitID(s), s) | |
| 20 | 21 | } | |
| @@ -26,6 +27,7 @@ func TestIsCommitID(t *testing.T) { | |||
| 26 | 27 | // The ref switcher marks the ref the page shows, whatever the page: a tag, a | |
| 27 | 28 | // branch other than the default one, or none for a commit. | |
| 28 | 29 | func TestPinRefMarksCurrent(t *testing.T) { | |
| 30 | + | t.Parallel() | |
| 29 | 31 | b, tag := pb.RefKind_REF_KIND_BRANCH, pb.RefKind_REF_KIND_TAG | |
| 30 | 32 | d := &repoData{ | |
| 31 | 33 | repo: &pb.Repo{Name: "demo", DefaultBranch: "main"}, | |
| @@ -15,6 +15,7 @@ import ( | |||||
| 15 | ) | 15 | ) | ||
| 16 | 16 | ||||
| 17 | func TestIsCommitID(t *testing.T) { | 17 | func TestIsCommitID(t *testing.T) { | ||
| 18 | + | t.Parallel() | |||
| 18 | for _, s := range []string{"abcdef0", "0123456789abcdef0123456789abcdef01234567"} { | 19 | for _, s := range []string{"abcdef0", "0123456789abcdef0123456789abcdef01234567"} { | ||
| 19 | assert.True(t, isCommitID(s), s) | 20 | assert.True(t, isCommitID(s), s) | ||
| 20 | } | 21 | } | ||
| @@ -26,6 +27,7 @@ func TestIsCommitID(t *testing.T) { | |||||
| 26 | // The ref switcher marks the ref the page shows, whatever the page: a tag, a | 27 | // The ref switcher marks the ref the page shows, whatever the page: a tag, a | ||
| 27 | // branch other than the default one, or none for a commit. | 28 | // branch other than the default one, or none for a commit. | ||
| 28 | func TestPinRefMarksCurrent(t *testing.T) { | 29 | func TestPinRefMarksCurrent(t *testing.T) { | ||
| 30 | + | t.Parallel() | |||
| 29 | b, tag := pb.RefKind_REF_KIND_BRANCH, pb.RefKind_REF_KIND_TAG | 31 | b, tag := pb.RefKind_REF_KIND_BRANCH, pb.RefKind_REF_KIND_TAG | ||
| 30 | d := &repoData{ | 32 | d := &repoData{ | ||
| 31 | repo: &pb.Repo{Name: "demo", DefaultBranch: "main"}, | 33 | repo: &pb.Repo{Name: "demo", DefaultBranch: "main"}, | ||
M internal/adapters/web/compress_test.go +13 -0
| @@ -22,6 +22,7 @@ import ( | |||
| 22 | 22 | ) | |
| 23 | 23 | ||
| 24 | 24 | func TestAcceptsGzip(t *testing.T) { | |
| 25 | + | t.Parallel() | |
| 25 | 26 | for _, tc := range []struct { | |
| 26 | 27 | name string | |
| 27 | 28 | header []string | |
| @@ -47,6 +48,7 @@ func TestAcceptsGzip(t *testing.T) { | |||
| 47 | 48 | {"substring is not a coding", []string{"gzipped"}, false}, | |
| 48 | 49 | } { | |
| 49 | 50 | t.Run(tc.name, func(t *testing.T) { | |
| 51 | + | t.Parallel() | |
| 50 | 52 | h := http.Header{} | |
| 51 | 53 | for _, v := range tc.header { | |
| 52 | 54 | h.Add("Accept-Encoding", v) | |
| @@ -57,6 +59,7 @@ func TestAcceptsGzip(t *testing.T) { | |||
| 57 | 59 | } | |
| 58 | 60 | ||
| 59 | 61 | func TestEtagMatches(t *testing.T) { | |
| 62 | + | t.Parallel() | |
| 60 | 63 | const tag = `"abc"` | |
| 61 | 64 | for _, tc := range []struct { | |
| 62 | 65 | name, header, etag string | |
| @@ -77,12 +80,14 @@ func TestEtagMatches(t *testing.T) { | |||
| 77 | 80 | {"comma in a tag", `"a,b", "abc"`, tag, true}, | |
| 78 | 81 | } { | |
| 79 | 82 | t.Run(tc.name, func(t *testing.T) { | |
| 83 | + | t.Parallel() | |
| 80 | 84 | assert.Equal(t, tc.want, etagMatches(tc.header, tc.etag)) | |
| 81 | 85 | }) | |
| 82 | 86 | } | |
| 83 | 87 | } | |
| 84 | 88 | ||
| 85 | 89 | func TestPageETag(t *testing.T) { | |
| 90 | + | t.Parallel() | |
| 86 | 91 | body := []byte("<html>x</html>") | |
| 87 | 92 | a := pageETag(body, false, false) | |
| 88 | 93 | assert.Equal(t, a, pageETag(body, false, false), "stable") | |
| @@ -113,6 +118,7 @@ func gunzip(t *testing.T, b []byte) string { | |||
| 113 | 118 | ||
| 114 | 119 | // write: gzip by size and Accept-Encoding, validators, 304. | |
| 115 | 120 | func TestWritePage(t *testing.T) { | |
| 121 | + | t.Parallel() | |
| 116 | 122 | big := "<p>" + strings.Repeat("ermite page ", 400) + "</p>" | |
| 117 | 123 | small := "<p>tiny</p>" | |
| 118 | 124 | ||
| @@ -137,6 +143,7 @@ func TestWritePage(t *testing.T) { | |||
| 137 | 143 | {"no-transform: untouched", "GET", "gzip", big, 200, "no-transform", false, true}, | |
| 138 | 144 | } { | |
| 139 | 145 | t.Run(tc.name, func(t *testing.T) { | |
| 146 | + | t.Parallel() | |
| 140 | 147 | srv := newBareServer(t) | |
| 141 | 148 | rec := httptest.NewRecorder() | |
| 142 | 149 | r := httptest.NewRequestWithContext(context.Background(), tc.method, "/", nil) | |
| @@ -172,6 +179,7 @@ func TestWritePage(t *testing.T) { | |||
| 172 | 179 | } | |
| 173 | 180 | ||
| 174 | 181 | func TestWritePageConditional(t *testing.T) { | |
| 182 | + | t.Parallel() | |
| 175 | 183 | big := "<p>" + strings.Repeat("ermite page ", 400) + "</p>" | |
| 176 | 184 | srv := newBareServer(t) | |
| 177 | 185 | do := func(ae, inm string) *http.Response { | |
| @@ -207,6 +215,7 @@ func TestWritePageConditional(t *testing.T) { | |||
| 207 | 215 | {"stale", "", `"stale"`, 200}, | |
| 208 | 216 | } { | |
| 209 | 217 | t.Run(tc.name, func(t *testing.T) { | |
| 218 | + | t.Parallel() | |
| 210 | 219 | res := do(tc.ae, tc.inm) | |
| 211 | 220 | assert.Equal(t, tc.want, res.StatusCode) | |
| 212 | 221 | b, _ := io.ReadAll(res.Body) | |
| @@ -225,6 +234,7 @@ func TestWritePageConditional(t *testing.T) { | |||
| 225 | 234 | ||
| 226 | 235 | // The owner's page is private and never revalidated, whatever the client sends. | |
| 227 | 236 | func TestWritePageOwnerHasNoValidator(t *testing.T) { | |
| 237 | + | t.Parallel() | |
| 228 | 238 | srv := newBareServer(t) | |
| 229 | 239 | ctx := context.WithValue(context.Background(), signedInKey{}, struct{}{}) | |
| 230 | 240 | rec := httptest.NewRecorder() | |
| @@ -240,6 +250,7 @@ func TestWritePageOwnerHasNoValidator(t *testing.T) { | |||
| 240 | 250 | ||
| 241 | 251 | // The assets: gzip once at load, chosen by Accept-Encoding, not for fonts. | |
| 242 | 252 | func TestAssetsGzip(t *testing.T) { | |
| 253 | + | t.Parallel() | |
| 243 | 254 | srv := newBareServer(t) | |
| 244 | 255 | get := func(url, ae string) *httptest.ResponseRecorder { | |
| 245 | 256 | rec := httptest.NewRecorder() | |
| @@ -265,6 +276,7 @@ func TestAssetsGzip(t *testing.T) { | |||
| 265 | 276 | {"font never", u.Font, "gzip", false, false}, | |
| 266 | 277 | } { | |
| 267 | 278 | t.Run(tc.name, func(t *testing.T) { | |
| 279 | + | t.Parallel() | |
| 268 | 280 | rec := get(tc.url, tc.ae) | |
| 269 | 281 | require.Equal(t, 200, rec.Code) | |
| 270 | 282 | assert.Equal(t, "public, max-age=31536000, immutable", rec.Header().Get("Cache-Control")) | |
| @@ -305,6 +317,7 @@ func TestAssetsGzip(t *testing.T) { | |||
| 305 | 317 | ||
| 306 | 318 | // The event stream is never compressed, even for a client that accepts gzip. | |
| 307 | 319 | func TestEventsNotCompressed(t *testing.T) { | |
| 320 | + | t.Parallel() | |
| 308 | 321 | f := &fakeEvents{release: make(chan struct{})} | |
| 309 | 322 | ts := eventsServer(t, f, 20*time.Millisecond, 0) | |
| 310 | 323 | resp, _ := open(t, ts, "topic=home", map[string]string{"Accept-Encoding": "gzip"}) | |
| @@ -22,6 +22,7 @@ import ( | |||||
| 22 | ) | 22 | ) | ||
| 23 | 23 | ||||
| 24 | func TestAcceptsGzip(t *testing.T) { | 24 | func TestAcceptsGzip(t *testing.T) { | ||
| 25 | + | t.Parallel() | |||
| 25 | for _, tc := range []struct { | 26 | for _, tc := range []struct { | ||
| 26 | name string | 27 | name string | ||
| 27 | header []string | 28 | header []string | ||
| @@ -47,6 +48,7 @@ func TestAcceptsGzip(t *testing.T) { | |||||
| 47 | {"substring is not a coding", []string{"gzipped"}, false}, | 48 | {"substring is not a coding", []string{"gzipped"}, false}, | ||
| 48 | } { | 49 | } { | ||
| 49 | t.Run(tc.name, func(t *testing.T) { | 50 | t.Run(tc.name, func(t *testing.T) { | ||
| 51 | + | t.Parallel() | |||
| 50 | h := http.Header{} | 52 | h := http.Header{} | ||
| 51 | for _, v := range tc.header { | 53 | for _, v := range tc.header { | ||
| 52 | h.Add("Accept-Encoding", v) | 54 | h.Add("Accept-Encoding", v) | ||
| @@ -57,6 +59,7 @@ func TestAcceptsGzip(t *testing.T) { | |||||
| 57 | } | 59 | } | ||
| 58 | 60 | ||||
| 59 | func TestEtagMatches(t *testing.T) { | 61 | func TestEtagMatches(t *testing.T) { | ||
| 62 | + | t.Parallel() | |||
| 60 | const tag = `"abc"` | 63 | const tag = `"abc"` | ||
| 61 | for _, tc := range []struct { | 64 | for _, tc := range []struct { | ||
| 62 | name, header, etag string | 65 | name, header, etag string | ||
| @@ -77,12 +80,14 @@ func TestEtagMatches(t *testing.T) { | |||||
| 77 | {"comma in a tag", `"a,b", "abc"`, tag, true}, | 80 | {"comma in a tag", `"a,b", "abc"`, tag, true}, | ||
| 78 | } { | 81 | } { | ||
| 79 | t.Run(tc.name, func(t *testing.T) { | 82 | t.Run(tc.name, func(t *testing.T) { | ||
| 83 | + | t.Parallel() | |||
| 80 | assert.Equal(t, tc.want, etagMatches(tc.header, tc.etag)) | 84 | assert.Equal(t, tc.want, etagMatches(tc.header, tc.etag)) | ||
| 81 | }) | 85 | }) | ||
| 82 | } | 86 | } | ||
| 83 | } | 87 | } | ||
| 84 | 88 | ||||
| 85 | func TestPageETag(t *testing.T) { | 89 | func TestPageETag(t *testing.T) { | ||
| 90 | + | t.Parallel() | |||
| 86 | body := []byte("<html>x</html>") | 91 | body := []byte("<html>x</html>") | ||
| 87 | a := pageETag(body, false, false) | 92 | a := pageETag(body, false, false) | ||
| 88 | assert.Equal(t, a, pageETag(body, false, false), "stable") | 93 | assert.Equal(t, a, pageETag(body, false, false), "stable") | ||
| @@ -113,6 +118,7 @@ func gunzip(t *testing.T, b []byte) string { | |||||
| 113 | 118 | ||||
| 114 | // write: gzip by size and Accept-Encoding, validators, 304. | 119 | // write: gzip by size and Accept-Encoding, validators, 304. | ||
| 115 | func TestWritePage(t *testing.T) { | 120 | func TestWritePage(t *testing.T) { | ||
| 121 | + | t.Parallel() | |||
| 116 | big := "<p>" + strings.Repeat("ermite page ", 400) + "</p>" | 122 | big := "<p>" + strings.Repeat("ermite page ", 400) + "</p>" | ||
| 117 | small := "<p>tiny</p>" | 123 | small := "<p>tiny</p>" | ||
| 118 | 124 | ||||
| @@ -137,6 +143,7 @@ func TestWritePage(t *testing.T) { | |||||
| 137 | {"no-transform: untouched", "GET", "gzip", big, 200, "no-transform", false, true}, | 143 | {"no-transform: untouched", "GET", "gzip", big, 200, "no-transform", false, true}, | ||
| 138 | } { | 144 | } { | ||
| 139 | t.Run(tc.name, func(t *testing.T) { | 145 | t.Run(tc.name, func(t *testing.T) { | ||
| 146 | + | t.Parallel() | |||
| 140 | srv := newBareServer(t) | 147 | srv := newBareServer(t) | ||
| 141 | rec := httptest.NewRecorder() | 148 | rec := httptest.NewRecorder() | ||
| 142 | r := httptest.NewRequestWithContext(context.Background(), tc.method, "/", nil) | 149 | r := httptest.NewRequestWithContext(context.Background(), tc.method, "/", nil) | ||
| @@ -172,6 +179,7 @@ func TestWritePage(t *testing.T) { | |||||
| 172 | } | 179 | } | ||
| 173 | 180 | ||||
| 174 | func TestWritePageConditional(t *testing.T) { | 181 | func TestWritePageConditional(t *testing.T) { | ||
| 182 | + | t.Parallel() | |||
| 175 | big := "<p>" + strings.Repeat("ermite page ", 400) + "</p>" | 183 | big := "<p>" + strings.Repeat("ermite page ", 400) + "</p>" | ||
| 176 | srv := newBareServer(t) | 184 | srv := newBareServer(t) | ||
| 177 | do := func(ae, inm string) *http.Response { | 185 | do := func(ae, inm string) *http.Response { | ||
| @@ -207,6 +215,7 @@ func TestWritePageConditional(t *testing.T) { | |||||
| 207 | {"stale", "", `"stale"`, 200}, | 215 | {"stale", "", `"stale"`, 200}, | ||
| 208 | } { | 216 | } { | ||
| 209 | t.Run(tc.name, func(t *testing.T) { | 217 | t.Run(tc.name, func(t *testing.T) { | ||
| 218 | + | t.Parallel() | |||
| 210 | res := do(tc.ae, tc.inm) | 219 | res := do(tc.ae, tc.inm) | ||
| 211 | assert.Equal(t, tc.want, res.StatusCode) | 220 | assert.Equal(t, tc.want, res.StatusCode) | ||
| 212 | b, _ := io.ReadAll(res.Body) | 221 | b, _ := io.ReadAll(res.Body) | ||
| @@ -225,6 +234,7 @@ func TestWritePageConditional(t *testing.T) { | |||||
| 225 | 234 | ||||
| 226 | // The owner's page is private and never revalidated, whatever the client sends. | 235 | // The owner's page is private and never revalidated, whatever the client sends. | ||
| 227 | func TestWritePageOwnerHasNoValidator(t *testing.T) { | 236 | func TestWritePageOwnerHasNoValidator(t *testing.T) { | ||
| 237 | + | t.Parallel() | |||
| 228 | srv := newBareServer(t) | 238 | srv := newBareServer(t) | ||
| 229 | ctx := context.WithValue(context.Background(), signedInKey{}, struct{}{}) | 239 | ctx := context.WithValue(context.Background(), signedInKey{}, struct{}{}) | ||
| 230 | rec := httptest.NewRecorder() | 240 | rec := httptest.NewRecorder() | ||
| @@ -240,6 +250,7 @@ func TestWritePageOwnerHasNoValidator(t *testing.T) { | |||||
| 240 | 250 | ||||
| 241 | // The assets: gzip once at load, chosen by Accept-Encoding, not for fonts. | 251 | // The assets: gzip once at load, chosen by Accept-Encoding, not for fonts. | ||
| 242 | func TestAssetsGzip(t *testing.T) { | 252 | func TestAssetsGzip(t *testing.T) { | ||
| 253 | + | t.Parallel() | |||
| 243 | srv := newBareServer(t) | 254 | srv := newBareServer(t) | ||
| 244 | get := func(url, ae string) *httptest.ResponseRecorder { | 255 | get := func(url, ae string) *httptest.ResponseRecorder { | ||
| 245 | rec := httptest.NewRecorder() | 256 | rec := httptest.NewRecorder() | ||
| @@ -265,6 +276,7 @@ func TestAssetsGzip(t *testing.T) { | |||||
| 265 | {"font never", u.Font, "gzip", false, false}, | 276 | {"font never", u.Font, "gzip", false, false}, | ||
| 266 | } { | 277 | } { | ||
| 267 | t.Run(tc.name, func(t *testing.T) { | 278 | t.Run(tc.name, func(t *testing.T) { | ||
| 279 | + | t.Parallel() | |||
| 268 | rec := get(tc.url, tc.ae) | 280 | rec := get(tc.url, tc.ae) | ||
| 269 | require.Equal(t, 200, rec.Code) | 281 | require.Equal(t, 200, rec.Code) | ||
| 270 | assert.Equal(t, "public, max-age=31536000, immutable", rec.Header().Get("Cache-Control")) | 282 | assert.Equal(t, "public, max-age=31536000, immutable", rec.Header().Get("Cache-Control")) | ||
| @@ -305,6 +317,7 @@ func TestAssetsGzip(t *testing.T) { | |||||
| 305 | 317 | ||||
| 306 | // The event stream is never compressed, even for a client that accepts gzip. | 318 | // The event stream is never compressed, even for a client that accepts gzip. | ||
| 307 | func TestEventsNotCompressed(t *testing.T) { | 319 | func TestEventsNotCompressed(t *testing.T) { | ||
| 320 | + | t.Parallel() | |||
| 308 | f := &fakeEvents{release: make(chan struct{})} | 321 | f := &fakeEvents{release: make(chan struct{})} | ||
| 309 | ts := eventsServer(t, f, 20*time.Millisecond, 0) | 322 | ts := eventsServer(t, f, 20*time.Millisecond, 0) | ||
| 310 | resp, _ := open(t, ts, "topic=home", map[string]string{"Accept-Encoding": "gzip"}) | 323 | resp, _ := open(t, ts, "topic=home", map[string]string{"Accept-Encoding": "gzip"}) | ||
M internal/adapters/web/diff_test.go +7 -0
| @@ -17,6 +17,7 @@ import ( | |||
| 17 | 17 | // characters are shown as markers; ordinary text, tabs and non-Latin letters | |
| 18 | 18 | // are not touched. | |
| 19 | 19 | func TestSegmentsShowInvisibleCharacters(t *testing.T) { | |
| 20 | + | t.Parallel() | |
| 20 | 21 | tests := []struct { | |
| 21 | 22 | name, in string | |
| 22 | 23 | want []Seg | |
| @@ -35,6 +36,7 @@ func TestSegmentsShowInvisibleCharacters(t *testing.T) { | |||
| 35 | 36 | } | |
| 36 | 37 | for _, tt := range tests { | |
| 37 | 38 | t.Run(tt.name, func(t *testing.T) { | |
| 39 | + | t.Parallel() | |
| 38 | 40 | assert.Equal(t, tt.want, segments(tt.in)) | |
| 39 | 41 | }) | |
| 40 | 42 | } | |
| @@ -51,6 +53,7 @@ func line(kind pb.LineKind, oldN, newN int32, text string) *pb.Line { | |||
| 51 | 53 | // context fills both sides; the extra lines of the longer run have an empty | |
| 52 | 54 | // opposite side. | |
| 53 | 55 | func TestPairLines(t *testing.T) { | |
| 56 | + | t.Parallel() | |
| 54 | 57 | ctx, add, del := pb.LineKind_LINE_KIND_CONTEXT, pb.LineKind_LINE_KIND_ADDITION, pb.LineKind_LINE_KIND_DELETION | |
| 55 | 58 | var ls []DiffLine | |
| 56 | 59 | for _, l := range []*pb.Line{ | |
| @@ -83,6 +86,7 @@ func TestPairLines(t *testing.T) { | |||
| 83 | 86 | } | |
| 84 | 87 | ||
| 85 | 88 | func TestChangeBar(t *testing.T) { | |
| 89 | + | t.Parallel() | |
| 86 | 90 | assert.Equal(t, Bar{Rest: "··········"}, changeBar(0, 0, 0)) | |
| 87 | 91 | assert.Equal(t, Bar{Add: "++++++++++"}, changeBar(100, 0, 100)) | |
| 88 | 92 | assert.Equal(t, Bar{Add: "+++++", Del: "-----", Rest: ""}, changeBar(50, 50, 100)) | |
| @@ -91,6 +95,7 @@ func TestChangeBar(t *testing.T) { | |||
| 91 | 95 | } | |
| 92 | 96 | ||
| 93 | 97 | func TestFileNotes(t *testing.T) { | |
| 98 | + | t.Parallel() | |
| 94 | 99 | assert.Equal(t, "binary file: no text diff", fileNote(&pb.FileDiff{Binary: true, Status: pb.FileStatus_FILE_STATUS_MODIFIED})) | |
| 95 | 100 | assert.Equal(t, "mode 100644 → 100755", fileNote(&pb.FileDiff{Status: pb.FileStatus_FILE_STATUS_MODIFIED, OldMode: 0o100644, NewMode: 0o100755})) | |
| 96 | 101 | assert.Empty(t, fileNote(&pb.FileDiff{Status: pb.FileStatus_FILE_STATUS_ADDED, NewMode: 0o100644}), "an added file has no mode change") | |
| @@ -99,6 +104,7 @@ func TestFileNotes(t *testing.T) { | |||
| 99 | 104 | } | |
| 100 | 105 | ||
| 101 | 106 | func TestDiffFilesPaths(t *testing.T) { | |
| 107 | + | t.Parallel() | |
| 102 | 108 | fs := diffFiles([]*pb.FileDiff{ | |
| 103 | 109 | {Status: pb.FileStatus_FILE_STATUS_ADDED, NewPath: "a.go", Additions: 1, Hunks: []*pb.Hunk{{Lines: []*pb.Line{line(pb.LineKind_LINE_KIND_ADDITION, 0, 1, "x")}}}}, | |
| 104 | 110 | {Status: pb.FileStatus_FILE_STATUS_DELETED, OldPath: "b.go", Deletions: 1}, | |
| @@ -117,6 +123,7 @@ func TestDiffFilesPaths(t *testing.T) { | |||
| 117 | 123 | // A code cell holds exactly the text: escaped, with markers, and nothing | |
| 118 | 124 | // added between the pieces (the cell keeps its white space). | |
| 119 | 125 | func TestCodeLine(t *testing.T) { | |
| 126 | + | t.Parallel() | |
| 120 | 127 | var b strings.Builder | |
| 121 | 128 | ln := diffLine(&pb.Line{Kind: pb.LineKind_LINE_KIND_ADDITION, NewNumber: 1, Text: "a <b> \u202e c d", NoNewlineAtEof: true}) | |
| 122 | 129 | require.NoError(t, codeLine(ln).Render(context.Background(), &b)) | |
| @@ -17,6 +17,7 @@ import ( | |||||
| 17 | // characters are shown as markers; ordinary text, tabs and non-Latin letters | 17 | // characters are shown as markers; ordinary text, tabs and non-Latin letters | ||
| 18 | // are not touched. | 18 | // are not touched. | ||
| 19 | func TestSegmentsShowInvisibleCharacters(t *testing.T) { | 19 | func TestSegmentsShowInvisibleCharacters(t *testing.T) { | ||
| 20 | + | t.Parallel() | |||
| 20 | tests := []struct { | 21 | tests := []struct { | ||
| 21 | name, in string | 22 | name, in string | ||
| 22 | want []Seg | 23 | want []Seg | ||
| @@ -35,6 +36,7 @@ func TestSegmentsShowInvisibleCharacters(t *testing.T) { | |||||
| 35 | } | 36 | } | ||
| 36 | for _, tt := range tests { | 37 | for _, tt := range tests { | ||
| 37 | t.Run(tt.name, func(t *testing.T) { | 38 | t.Run(tt.name, func(t *testing.T) { | ||
| 39 | + | t.Parallel() | |||
| 38 | assert.Equal(t, tt.want, segments(tt.in)) | 40 | assert.Equal(t, tt.want, segments(tt.in)) | ||
| 39 | }) | 41 | }) | ||
| 40 | } | 42 | } | ||
| @@ -51,6 +53,7 @@ func line(kind pb.LineKind, oldN, newN int32, text string) *pb.Line { | |||||
| 51 | // context fills both sides; the extra lines of the longer run have an empty | 53 | // context fills both sides; the extra lines of the longer run have an empty | ||
| 52 | // opposite side. | 54 | // opposite side. | ||
| 53 | func TestPairLines(t *testing.T) { | 55 | func TestPairLines(t *testing.T) { | ||
| 56 | + | t.Parallel() | |||
| 54 | ctx, add, del := pb.LineKind_LINE_KIND_CONTEXT, pb.LineKind_LINE_KIND_ADDITION, pb.LineKind_LINE_KIND_DELETION | 57 | ctx, add, del := pb.LineKind_LINE_KIND_CONTEXT, pb.LineKind_LINE_KIND_ADDITION, pb.LineKind_LINE_KIND_DELETION | ||
| 55 | var ls []DiffLine | 58 | var ls []DiffLine | ||
| 56 | for _, l := range []*pb.Line{ | 59 | for _, l := range []*pb.Line{ | ||
| @@ -83,6 +86,7 @@ func TestPairLines(t *testing.T) { | |||||
| 83 | } | 86 | } | ||
| 84 | 87 | ||||
| 85 | func TestChangeBar(t *testing.T) { | 88 | func TestChangeBar(t *testing.T) { | ||
| 89 | + | t.Parallel() | |||
| 86 | assert.Equal(t, Bar{Rest: "··········"}, changeBar(0, 0, 0)) | 90 | assert.Equal(t, Bar{Rest: "··········"}, changeBar(0, 0, 0)) | ||
| 87 | assert.Equal(t, Bar{Add: "++++++++++"}, changeBar(100, 0, 100)) | 91 | assert.Equal(t, Bar{Add: "++++++++++"}, changeBar(100, 0, 100)) | ||
| 88 | assert.Equal(t, Bar{Add: "+++++", Del: "-----", Rest: ""}, changeBar(50, 50, 100)) | 92 | assert.Equal(t, Bar{Add: "+++++", Del: "-----", Rest: ""}, changeBar(50, 50, 100)) | ||
| @@ -91,6 +95,7 @@ func TestChangeBar(t *testing.T) { | |||||
| 91 | } | 95 | } | ||
| 92 | 96 | ||||
| 93 | func TestFileNotes(t *testing.T) { | 97 | func TestFileNotes(t *testing.T) { | ||
| 98 | + | t.Parallel() | |||
| 94 | assert.Equal(t, "binary file: no text diff", fileNote(&pb.FileDiff{Binary: true, Status: pb.FileStatus_FILE_STATUS_MODIFIED})) | 99 | assert.Equal(t, "binary file: no text diff", fileNote(&pb.FileDiff{Binary: true, Status: pb.FileStatus_FILE_STATUS_MODIFIED})) | ||
| 95 | assert.Equal(t, "mode 100644 → 100755", fileNote(&pb.FileDiff{Status: pb.FileStatus_FILE_STATUS_MODIFIED, OldMode: 0o100644, NewMode: 0o100755})) | 100 | assert.Equal(t, "mode 100644 → 100755", fileNote(&pb.FileDiff{Status: pb.FileStatus_FILE_STATUS_MODIFIED, OldMode: 0o100644, NewMode: 0o100755})) | ||
| 96 | assert.Empty(t, fileNote(&pb.FileDiff{Status: pb.FileStatus_FILE_STATUS_ADDED, NewMode: 0o100644}), "an added file has no mode change") | 101 | assert.Empty(t, fileNote(&pb.FileDiff{Status: pb.FileStatus_FILE_STATUS_ADDED, NewMode: 0o100644}), "an added file has no mode change") | ||
| @@ -99,6 +104,7 @@ func TestFileNotes(t *testing.T) { | |||||
| 99 | } | 104 | } | ||
| 100 | 105 | ||||
| 101 | func TestDiffFilesPaths(t *testing.T) { | 106 | func TestDiffFilesPaths(t *testing.T) { | ||
| 107 | + | t.Parallel() | |||
| 102 | fs := diffFiles([]*pb.FileDiff{ | 108 | fs := diffFiles([]*pb.FileDiff{ | ||
| 103 | {Status: pb.FileStatus_FILE_STATUS_ADDED, NewPath: "a.go", Additions: 1, Hunks: []*pb.Hunk{{Lines: []*pb.Line{line(pb.LineKind_LINE_KIND_ADDITION, 0, 1, "x")}}}}, | 109 | {Status: pb.FileStatus_FILE_STATUS_ADDED, NewPath: "a.go", Additions: 1, Hunks: []*pb.Hunk{{Lines: []*pb.Line{line(pb.LineKind_LINE_KIND_ADDITION, 0, 1, "x")}}}}, | ||
| 104 | {Status: pb.FileStatus_FILE_STATUS_DELETED, OldPath: "b.go", Deletions: 1}, | 110 | {Status: pb.FileStatus_FILE_STATUS_DELETED, OldPath: "b.go", Deletions: 1}, | ||
| @@ -117,6 +123,7 @@ func TestDiffFilesPaths(t *testing.T) { | |||||
| 117 | // A code cell holds exactly the text: escaped, with markers, and nothing | 123 | // A code cell holds exactly the text: escaped, with markers, and nothing | ||
| 118 | // added between the pieces (the cell keeps its white space). | 124 | // added between the pieces (the cell keeps its white space). | ||
| 119 | func TestCodeLine(t *testing.T) { | 125 | func TestCodeLine(t *testing.T) { | ||
| 126 | + | t.Parallel() | |||
| 120 | var b strings.Builder | 127 | var b strings.Builder | ||
| 121 | ln := diffLine(&pb.Line{Kind: pb.LineKind_LINE_KIND_ADDITION, NewNumber: 1, Text: "a <b> \u202e c d", NoNewlineAtEof: true}) | 128 | ln := diffLine(&pb.Line{Kind: pb.LineKind_LINE_KIND_ADDITION, NewNumber: 1, Text: "a <b> \u202e c d", NoNewlineAtEof: true}) | ||
| 122 | require.NoError(t, codeLine(ln).Render(context.Background(), &b)) | 129 | require.NoError(t, codeLine(ln).Render(context.Background(), &b)) | ||
M internal/adapters/web/errors_test.go +2 -0
| @@ -51,6 +51,7 @@ func serve(t *testing.T, err error, path string) (*httptest.ResponseRecorder, st | |||
| 51 | 51 | // Errors of the API: not found (and an invalid name) are the 404 page, any | |
| 52 | 52 | // other is the 500 page with an id that the log carries. | |
| 53 | 53 | func TestErrorPages(t *testing.T) { | |
| 54 | + | t.Parallel() | |
| 54 | 55 | rec, logs := serve(t, connect.NewError(connect.CodeNotFound, errors.New("repository not found")), "/gone") | |
| 55 | 56 | assert.Equal(t, http.StatusNotFound, rec.Code) | |
| 56 | 57 | assert.Contains(t, rec.Body.String(), "not found") | |
| @@ -76,6 +77,7 @@ func TestErrorPages(t *testing.T) { | |||
| 76 | 77 | } | |
| 77 | 78 | ||
| 78 | 79 | func TestNewValidates(t *testing.T) { | |
| 80 | + | t.Parallel() | |
| 79 | 81 | _, err := New(Config{PublicURL: "https://forge.example"}) | |
| 80 | 82 | assert.Error(t, err, "clients are required") | |
| 81 | 83 | git := ermiteconnect.NewGitServiceClient(http.DefaultClient, "http://x.invalid") | |
| @@ -51,6 +51,7 @@ func serve(t *testing.T, err error, path string) (*httptest.ResponseRecorder, st | |||||
| 51 | // Errors of the API: not found (and an invalid name) are the 404 page, any | 51 | // Errors of the API: not found (and an invalid name) are the 404 page, any | ||
| 52 | // other is the 500 page with an id that the log carries. | 52 | // other is the 500 page with an id that the log carries. | ||
| 53 | func TestErrorPages(t *testing.T) { | 53 | func TestErrorPages(t *testing.T) { | ||
| 54 | + | t.Parallel() | |||
| 54 | rec, logs := serve(t, connect.NewError(connect.CodeNotFound, errors.New("repository not found")), "/gone") | 55 | rec, logs := serve(t, connect.NewError(connect.CodeNotFound, errors.New("repository not found")), "/gone") | ||
| 55 | assert.Equal(t, http.StatusNotFound, rec.Code) | 56 | assert.Equal(t, http.StatusNotFound, rec.Code) | ||
| 56 | assert.Contains(t, rec.Body.String(), "not found") | 57 | assert.Contains(t, rec.Body.String(), "not found") | ||
| @@ -76,6 +77,7 @@ func TestErrorPages(t *testing.T) { | |||||
| 76 | } | 77 | } | ||
| 77 | 78 | ||||
| 78 | func TestNewValidates(t *testing.T) { | 79 | func TestNewValidates(t *testing.T) { | ||
| 80 | + | t.Parallel() | |||
| 79 | _, err := New(Config{PublicURL: "https://forge.example"}) | 81 | _, err := New(Config{PublicURL: "https://forge.example"}) | ||
| 80 | assert.Error(t, err, "clients are required") | 82 | assert.Error(t, err, "clients are required") | ||
| 81 | git := ermiteconnect.NewGitServiceClient(http.DefaultClient, "http://x.invalid") | 83 | git := ermiteconnect.NewGitServiceClient(http.DefaultClient, "http://x.invalid") | ||
M internal/adapters/web/events_test.go +13 -0
| @@ -23,6 +23,7 @@ import ( | |||
| 23 | 23 | ) | |
| 24 | 24 | ||
| 25 | 25 | func TestFormatEvent(t *testing.T) { | |
| 26 | + | t.Parallel() | |
| 26 | 27 | assert.Equal(t, "event: home-list\nid: b-1\ndata: <tr id=\"x\">\ndata: <td>1</td>\ndata: </tr>\n\n", | |
| 27 | 28 | formatEvent("b-1", "home-list", "<tr id=\"x\">\n <td>1</td>\n</tr>", true), "one data line per line") | |
| 28 | 29 | assert.Equal(t, "event: gap\ndata: gap\n\n", formatEvent("", "gap", "gap", true)) | |
| @@ -32,6 +33,7 @@ func TestFormatEvent(t *testing.T) { | |||
| 32 | 33 | } | |
| 33 | 34 | ||
| 34 | 35 | func TestParseTopic(t *testing.T) { | |
| 36 | + | t.Parallel() | |
| 35 | 37 | for in, want := range map[string]string{"home": "", "repo:x": "x", "repo:a.b-c": "a.b-c"} { | |
| 36 | 38 | repo, ok := parseTopic(in) | |
| 37 | 39 | assert.True(t, ok, in) | |
| @@ -44,11 +46,13 @@ func TestParseTopic(t *testing.T) { | |||
| 44 | 46 | } | |
| 45 | 47 | ||
| 46 | 48 | func TestLiveURL(t *testing.T) { | |
| 49 | + | t.Parallel() | |
| 47 | 50 | assert.Equal(t, "/-/events?topic=home", liveURL("home", "")) | |
| 48 | 51 | assert.Equal(t, "/-/events?topic=repo%3Ax&view=refs", liveURL("repo:x", "view=refs")) | |
| 49 | 52 | } | |
| 50 | 53 | ||
| 51 | 54 | func TestNewCommitsText(t *testing.T) { | |
| 55 | + | t.Parallel() | |
| 52 | 56 | assert.Equal(t, "1 new commit — reload", newCommits("1", 1)) | |
| 53 | 57 | assert.Equal(t, "3 new commits — reload", newCommits("3", 3)) | |
| 54 | 58 | assert.Equal(t, "100+ new commits — reload", newCommits("100+", 2)) | |
| @@ -150,6 +154,7 @@ func open(t *testing.T, ts *httptest.Server, query string, hdr map[string]string | |||
| 150 | 154 | // An idle stream gets a comment every heartbeat, and the write timeout of the | |
| 151 | 155 | // server does not end it. | |
| 152 | 156 | func TestEventsHeartbeatSurvivesWriteTimeout(t *testing.T) { | |
| 157 | + | t.Parallel() | |
| 153 | 158 | f := &fakeEvents{release: make(chan struct{})} | |
| 154 | 159 | ts := eventsServer(t, f, 40*time.Millisecond, 100*time.Millisecond) | |
| 155 | 160 | resp, cancel := open(t, ts, "topic=home", nil) | |
| @@ -173,6 +178,7 @@ func TestEventsHeartbeatSurvivesWriteTimeout(t *testing.T) { | |||
| 173 | 178 | // The id of the event, its name and the lines of its data come through; the | |
| 174 | 179 | // Last-Event-ID header is the resume point; a Gap becomes a gap event. | |
| 175 | 180 | func TestEventsStreamsEventsAndResumes(t *testing.T) { | |
| 181 | + | t.Parallel() | |
| 176 | 182 | f := &fakeEvents{release: make(chan struct{}), events: []*pb.Event{ | |
| 177 | 183 | {Id: "b-7", Kind: &pb.Event_Pushed{Pushed: &pb.Pushed{Repo: "x"}}}, | |
| 178 | 184 | {Kind: &pb.Event_Gap{Gap: &pb.Gap{}}}, | |
| @@ -195,6 +201,7 @@ func TestEventsStreamsEventsAndResumes(t *testing.T) { | |||
| 195 | 201 | ||
| 196 | 202 | // Too many watchers is a 503 the browser can retry, not a stream. | |
| 197 | 203 | func TestEventsResourceExhausted(t *testing.T) { | |
| 204 | + | t.Parallel() | |
| 198 | 205 | f := &fakeEvents{err: connect.NewError(connect.CodeResourceExhausted, errors.New("too many watchers"))} | |
| 199 | 206 | ts := eventsServer(t, f, time.Hour, 0) | |
| 200 | 207 | resp, _ := open(t, ts, "topic=home", nil) | |
| @@ -213,6 +220,7 @@ func TestEventsResourceExhausted(t *testing.T) { | |||
| 213 | 220 | // The handler returns, and the watch it opened ends, when the visitor goes | |
| 214 | 221 | // away or the API ends the stream. | |
| 215 | 222 | func TestEventsNoLeak(t *testing.T) { | |
| 223 | + | t.Parallel() | |
| 216 | 224 | f := &fakeEvents{release: make(chan struct{})} | |
| 217 | 225 | ts := eventsServer(t, f, time.Hour, 0) | |
| 218 | 226 | ||
| @@ -237,6 +245,7 @@ func TestEventsNoLeak(t *testing.T) { | |||
| 237 | 245 | } | |
| 238 | 246 | ||
| 239 | 247 | func TestEventsOffWithoutClient(t *testing.T) { | |
| 248 | + | t.Parallel() | |
| 240 | 249 | srv, err := New(Config{ | |
| 241 | 250 | Repos: ermiteconnect.NewRepoServiceClient(http.DefaultClient, "http://x.invalid"), | |
| 242 | 251 | Git: ermiteconnect.NewGitServiceClient(http.DefaultClient, "http://x.invalid"), | |
| @@ -252,6 +261,7 @@ func TestEventsOffWithoutClient(t *testing.T) { | |||
| 252 | 261 | // show it load themselves again: the summary and the refs page, whose rows | |
| 253 | 262 | // and marks depend on it. The commits page shows a branch, not the default. | |
| 254 | 263 | func TestRepoUpdatedReloadsSummaryAndRefs(t *testing.T) { | |
| 264 | + | t.Parallel() | |
| 255 | 265 | api := http.NewServeMux() | |
| 256 | 266 | api.Handle(ermiteconnect.NewRepoServiceHandler(repoOK{})) | |
| 257 | 267 | backend := httptest.NewServer(api) | |
| @@ -298,6 +308,7 @@ func (r *revocable) Verify(ctx context.Context, tok string) (context.Context, bo | |||
| 298 | 308 | // only on the heartbeat: after the session ended, the next event is not sent | |
| 299 | 309 | // and the stream ends. | |
| 300 | 310 | func TestEventsOwnerStreamEndsBeforeNextEvent(t *testing.T) { | |
| 311 | + | t.Parallel() | |
| 301 | 312 | f := &fakeEvents{release: make(chan struct{}), push: make(chan *pb.Event)} | |
| 302 | 313 | sess := &revocable{fakeSessions: fakeSessions{valid: "tok"}} | |
| 303 | 314 | ts := eventsServerWith(t, f, time.Hour, 0, sess) // no heartbeat in the test | |
| @@ -340,6 +351,7 @@ func TestEventsOwnerStreamEndsBeforeNextEvent(t *testing.T) { | |||
| 340 | 351 | ||
| 341 | 352 | // A visitor's stream does not depend on sessions: events keep flowing. | |
| 342 | 353 | func TestEventsVisitorStreamIgnoresSessions(t *testing.T) { | |
| 354 | + | t.Parallel() | |
| 343 | 355 | f := &fakeEvents{release: make(chan struct{}), push: make(chan *pb.Event)} | |
| 344 | 356 | sess := &revocable{fakeSessions: fakeSessions{valid: "tok"}} | |
| 345 | 357 | sess.ended.Store(true) | |
| @@ -361,6 +373,7 @@ func TestEventsVisitorStreamIgnoresSessions(t *testing.T) { | |||
| 361 | 373 | // GO-006: a stream opened with an invalid cookie is anonymous, and its headers | |
| 362 | 374 | // say the answer must not be stored and varies on the cookie. | |
| 363 | 375 | func TestEventsInvalidCookieStream(t *testing.T) { | |
| 376 | + | t.Parallel() | |
| 364 | 377 | f := &fakeEvents{release: make(chan struct{})} | |
| 365 | 378 | ts := eventsServerWith(t, f, time.Hour, 0, &fakeSessions{valid: "tok"}) | |
| 366 | 379 | resp, _ := open(t, ts, "topic=home", map[string]string{"Cookie": CookieName + "=bad"}) | |
| @@ -23,6 +23,7 @@ import ( | |||||
| 23 | ) | 23 | ) | ||
| 24 | 24 | ||||
| 25 | func TestFormatEvent(t *testing.T) { | 25 | func TestFormatEvent(t *testing.T) { | ||
| 26 | + | t.Parallel() | |||
| 26 | assert.Equal(t, "event: home-list\nid: b-1\ndata: <tr id=\"x\">\ndata: <td>1</td>\ndata: </tr>\n\n", | 27 | assert.Equal(t, "event: home-list\nid: b-1\ndata: <tr id=\"x\">\ndata: <td>1</td>\ndata: </tr>\n\n", | ||
| 27 | formatEvent("b-1", "home-list", "<tr id=\"x\">\n <td>1</td>\n</tr>", true), "one data line per line") | 28 | formatEvent("b-1", "home-list", "<tr id=\"x\">\n <td>1</td>\n</tr>", true), "one data line per line") | ||
| 28 | assert.Equal(t, "event: gap\ndata: gap\n\n", formatEvent("", "gap", "gap", true)) | 29 | assert.Equal(t, "event: gap\ndata: gap\n\n", formatEvent("", "gap", "gap", true)) | ||
| @@ -32,6 +33,7 @@ func TestFormatEvent(t *testing.T) { | |||||
| 32 | } | 33 | } | ||
| 33 | 34 | ||||
| 34 | func TestParseTopic(t *testing.T) { | 35 | func TestParseTopic(t *testing.T) { | ||
| 36 | + | t.Parallel() | |||
| 35 | for in, want := range map[string]string{"home": "", "repo:x": "x", "repo:a.b-c": "a.b-c"} { | 37 | for in, want := range map[string]string{"home": "", "repo:x": "x", "repo:a.b-c": "a.b-c"} { | ||
| 36 | repo, ok := parseTopic(in) | 38 | repo, ok := parseTopic(in) | ||
| 37 | assert.True(t, ok, in) | 39 | assert.True(t, ok, in) | ||
| @@ -44,11 +46,13 @@ func TestParseTopic(t *testing.T) { | |||||
| 44 | } | 46 | } | ||
| 45 | 47 | ||||
| 46 | func TestLiveURL(t *testing.T) { | 48 | func TestLiveURL(t *testing.T) { | ||
| 49 | + | t.Parallel() | |||
| 47 | assert.Equal(t, "/-/events?topic=home", liveURL("home", "")) | 50 | assert.Equal(t, "/-/events?topic=home", liveURL("home", "")) | ||
| 48 | assert.Equal(t, "/-/events?topic=repo%3Ax&view=refs", liveURL("repo:x", "view=refs")) | 51 | assert.Equal(t, "/-/events?topic=repo%3Ax&view=refs", liveURL("repo:x", "view=refs")) | ||
| 49 | } | 52 | } | ||
| 50 | 53 | ||||
| 51 | func TestNewCommitsText(t *testing.T) { | 54 | func TestNewCommitsText(t *testing.T) { | ||
| 55 | + | t.Parallel() | |||
| 52 | assert.Equal(t, "1 new commit — reload", newCommits("1", 1)) | 56 | assert.Equal(t, "1 new commit — reload", newCommits("1", 1)) | ||
| 53 | assert.Equal(t, "3 new commits — reload", newCommits("3", 3)) | 57 | assert.Equal(t, "3 new commits — reload", newCommits("3", 3)) | ||
| 54 | assert.Equal(t, "100+ new commits — reload", newCommits("100+", 2)) | 58 | assert.Equal(t, "100+ new commits — reload", newCommits("100+", 2)) | ||
| @@ -150,6 +154,7 @@ func open(t *testing.T, ts *httptest.Server, query string, hdr map[string]string | |||||
| 150 | // An idle stream gets a comment every heartbeat, and the write timeout of the | 154 | // An idle stream gets a comment every heartbeat, and the write timeout of the | ||
| 151 | // server does not end it. | 155 | // server does not end it. | ||
| 152 | func TestEventsHeartbeatSurvivesWriteTimeout(t *testing.T) { | 156 | func TestEventsHeartbeatSurvivesWriteTimeout(t *testing.T) { | ||
| 157 | + | t.Parallel() | |||
| 153 | f := &fakeEvents{release: make(chan struct{})} | 158 | f := &fakeEvents{release: make(chan struct{})} | ||
| 154 | ts := eventsServer(t, f, 40*time.Millisecond, 100*time.Millisecond) | 159 | ts := eventsServer(t, f, 40*time.Millisecond, 100*time.Millisecond) | ||
| 155 | resp, cancel := open(t, ts, "topic=home", nil) | 160 | resp, cancel := open(t, ts, "topic=home", nil) | ||
| @@ -173,6 +178,7 @@ func TestEventsHeartbeatSurvivesWriteTimeout(t *testing.T) { | |||||
| 173 | // The id of the event, its name and the lines of its data come through; the | 178 | // The id of the event, its name and the lines of its data come through; the | ||
| 174 | // Last-Event-ID header is the resume point; a Gap becomes a gap event. | 179 | // Last-Event-ID header is the resume point; a Gap becomes a gap event. | ||
| 175 | func TestEventsStreamsEventsAndResumes(t *testing.T) { | 180 | func TestEventsStreamsEventsAndResumes(t *testing.T) { | ||
| 181 | + | t.Parallel() | |||
| 176 | f := &fakeEvents{release: make(chan struct{}), events: []*pb.Event{ | 182 | f := &fakeEvents{release: make(chan struct{}), events: []*pb.Event{ | ||
| 177 | {Id: "b-7", Kind: &pb.Event_Pushed{Pushed: &pb.Pushed{Repo: "x"}}}, | 183 | {Id: "b-7", Kind: &pb.Event_Pushed{Pushed: &pb.Pushed{Repo: "x"}}}, | ||
| 178 | {Kind: &pb.Event_Gap{Gap: &pb.Gap{}}}, | 184 | {Kind: &pb.Event_Gap{Gap: &pb.Gap{}}}, | ||
| @@ -195,6 +201,7 @@ func TestEventsStreamsEventsAndResumes(t *testing.T) { | |||||
| 195 | 201 | ||||
| 196 | // Too many watchers is a 503 the browser can retry, not a stream. | 202 | // Too many watchers is a 503 the browser can retry, not a stream. | ||
| 197 | func TestEventsResourceExhausted(t *testing.T) { | 203 | func TestEventsResourceExhausted(t *testing.T) { | ||
| 204 | + | t.Parallel() | |||
| 198 | f := &fakeEvents{err: connect.NewError(connect.CodeResourceExhausted, errors.New("too many watchers"))} | 205 | f := &fakeEvents{err: connect.NewError(connect.CodeResourceExhausted, errors.New("too many watchers"))} | ||
| 199 | ts := eventsServer(t, f, time.Hour, 0) | 206 | ts := eventsServer(t, f, time.Hour, 0) | ||
| 200 | resp, _ := open(t, ts, "topic=home", nil) | 207 | resp, _ := open(t, ts, "topic=home", nil) | ||
| @@ -213,6 +220,7 @@ func TestEventsResourceExhausted(t *testing.T) { | |||||
| 213 | // The handler returns, and the watch it opened ends, when the visitor goes | 220 | // The handler returns, and the watch it opened ends, when the visitor goes | ||
| 214 | // away or the API ends the stream. | 221 | // away or the API ends the stream. | ||
| 215 | func TestEventsNoLeak(t *testing.T) { | 222 | func TestEventsNoLeak(t *testing.T) { | ||
| 223 | + | t.Parallel() | |||
| 216 | f := &fakeEvents{release: make(chan struct{})} | 224 | f := &fakeEvents{release: make(chan struct{})} | ||
| 217 | ts := eventsServer(t, f, time.Hour, 0) | 225 | ts := eventsServer(t, f, time.Hour, 0) | ||
| 218 | 226 | ||||
| @@ -237,6 +245,7 @@ func TestEventsNoLeak(t *testing.T) { | |||||
| 237 | } | 245 | } | ||
| 238 | 246 | ||||
| 239 | func TestEventsOffWithoutClient(t *testing.T) { | 247 | func TestEventsOffWithoutClient(t *testing.T) { | ||
| 248 | + | t.Parallel() | |||
| 240 | srv, err := New(Config{ | 249 | srv, err := New(Config{ | ||
| 241 | Repos: ermiteconnect.NewRepoServiceClient(http.DefaultClient, "http://x.invalid"), | 250 | Repos: ermiteconnect.NewRepoServiceClient(http.DefaultClient, "http://x.invalid"), | ||
| 242 | Git: ermiteconnect.NewGitServiceClient(http.DefaultClient, "http://x.invalid"), | 251 | Git: ermiteconnect.NewGitServiceClient(http.DefaultClient, "http://x.invalid"), | ||
| @@ -252,6 +261,7 @@ func TestEventsOffWithoutClient(t *testing.T) { | |||||
| 252 | // show it load themselves again: the summary and the refs page, whose rows | 261 | // show it load themselves again: the summary and the refs page, whose rows | ||
| 253 | // and marks depend on it. The commits page shows a branch, not the default. | 262 | // and marks depend on it. The commits page shows a branch, not the default. | ||
| 254 | func TestRepoUpdatedReloadsSummaryAndRefs(t *testing.T) { | 263 | func TestRepoUpdatedReloadsSummaryAndRefs(t *testing.T) { | ||
| 264 | + | t.Parallel() | |||
| 255 | api := http.NewServeMux() | 265 | api := http.NewServeMux() | ||
| 256 | api.Handle(ermiteconnect.NewRepoServiceHandler(repoOK{})) | 266 | api.Handle(ermiteconnect.NewRepoServiceHandler(repoOK{})) | ||
| 257 | backend := httptest.NewServer(api) | 267 | backend := httptest.NewServer(api) | ||
| @@ -298,6 +308,7 @@ func (r *revocable) Verify(ctx context.Context, tok string) (context.Context, bo | |||||
| 298 | // only on the heartbeat: after the session ended, the next event is not sent | 308 | // only on the heartbeat: after the session ended, the next event is not sent | ||
| 299 | // and the stream ends. | 309 | // and the stream ends. | ||
| 300 | func TestEventsOwnerStreamEndsBeforeNextEvent(t *testing.T) { | 310 | func TestEventsOwnerStreamEndsBeforeNextEvent(t *testing.T) { | ||
| 311 | + | t.Parallel() | |||
| 301 | f := &fakeEvents{release: make(chan struct{}), push: make(chan *pb.Event)} | 312 | f := &fakeEvents{release: make(chan struct{}), push: make(chan *pb.Event)} | ||
| 302 | sess := &revocable{fakeSessions: fakeSessions{valid: "tok"}} | 313 | sess := &revocable{fakeSessions: fakeSessions{valid: "tok"}} | ||
| 303 | ts := eventsServerWith(t, f, time.Hour, 0, sess) // no heartbeat in the test | 314 | ts := eventsServerWith(t, f, time.Hour, 0, sess) // no heartbeat in the test | ||
| @@ -340,6 +351,7 @@ func TestEventsOwnerStreamEndsBeforeNextEvent(t *testing.T) { | |||||
| 340 | 351 | ||||
| 341 | // A visitor's stream does not depend on sessions: events keep flowing. | 352 | // A visitor's stream does not depend on sessions: events keep flowing. | ||
| 342 | func TestEventsVisitorStreamIgnoresSessions(t *testing.T) { | 353 | func TestEventsVisitorStreamIgnoresSessions(t *testing.T) { | ||
| 354 | + | t.Parallel() | |||
| 343 | f := &fakeEvents{release: make(chan struct{}), push: make(chan *pb.Event)} | 355 | f := &fakeEvents{release: make(chan struct{}), push: make(chan *pb.Event)} | ||
| 344 | sess := &revocable{fakeSessions: fakeSessions{valid: "tok"}} | 356 | sess := &revocable{fakeSessions: fakeSessions{valid: "tok"}} | ||
| 345 | sess.ended.Store(true) | 357 | sess.ended.Store(true) | ||
| @@ -361,6 +373,7 @@ func TestEventsVisitorStreamIgnoresSessions(t *testing.T) { | |||||
| 361 | // GO-006: a stream opened with an invalid cookie is anonymous, and its headers | 373 | // GO-006: a stream opened with an invalid cookie is anonymous, and its headers | ||
| 362 | // say the answer must not be stored and varies on the cookie. | 374 | // say the answer must not be stored and varies on the cookie. | ||
| 363 | func TestEventsInvalidCookieStream(t *testing.T) { | 375 | func TestEventsInvalidCookieStream(t *testing.T) { | ||
| 376 | + | t.Parallel() | |||
| 364 | f := &fakeEvents{release: make(chan struct{})} | 377 | f := &fakeEvents{release: make(chan struct{})} | ||
| 365 | ts := eventsServerWith(t, f, time.Hour, 0, &fakeSessions{valid: "tok"}) | 378 | ts := eventsServerWith(t, f, time.Hour, 0, &fakeSessions{valid: "tok"}) | ||
| 366 | resp, _ := open(t, ts, "topic=home", map[string]string{"Cookie": CookieName + "=bad"}) | 379 | resp, _ := open(t, ts, "topic=home", map[string]string{"Cookie": CookieName + "=bad"}) | ||
M internal/adapters/web/fanout_test.go +7 -0
| @@ -22,6 +22,7 @@ import ( | |||
| 22 | 22 | func frag(s string) []fragment { return []fragment{{event: "refs-list", html: s}} } | |
| 23 | 23 | ||
| 24 | 24 | func TestFanoutBuildsOncePerKey(t *testing.T) { | |
| 25 | + | t.Parallel() | |
| 25 | 26 | srvA, srvB := &Server{}, &Server{} | |
| 26 | 27 | base := fanKey{srv: srvA, repo: "x", event: "b-1"} | |
| 27 | 28 | for _, tt := range []struct { | |
| @@ -37,6 +38,7 @@ func TestFanoutBuildsOncePerKey(t *testing.T) { | |||
| 37 | 38 | {"no event id: nothing to share by", []fanKey{{srv: srvA, repo: "x"}, {srv: srvA, repo: "x"}}, 2}, | |
| 38 | 39 | } { | |
| 39 | 40 | t.Run(tt.name, func(t *testing.T) { | |
| 41 | + | t.Parallel() | |
| 40 | 42 | var f fanout | |
| 41 | 43 | var built atomic.Int32 | |
| 42 | 44 | for _, k := range tt.keys { | |
| @@ -54,6 +56,7 @@ func TestFanoutBuildsOncePerKey(t *testing.T) { | |||
| 54 | 56 | } | |
| 55 | 57 | ||
| 56 | 58 | func TestFanoutConcurrentWatchersShareOneBuild(t *testing.T) { | |
| 59 | + | t.Parallel() | |
| 57 | 60 | var f fanout | |
| 58 | 61 | var built atomic.Int32 | |
| 59 | 62 | release := make(chan struct{}) | |
| @@ -80,6 +83,7 @@ func TestFanoutConcurrentWatchersShareOneBuild(t *testing.T) { | |||
| 80 | 83 | } | |
| 81 | 84 | ||
| 82 | 85 | func TestFanoutFailureIsNotKept(t *testing.T) { | |
| 86 | + | t.Parallel() | |
| 83 | 87 | var f fanout | |
| 84 | 88 | key := fanKey{srv: &Server{}, repo: "x", event: "b-1"} | |
| 85 | 89 | _, err := f.do(context.Background(), key, func() ([]fragment, error) { return nil, errors.New("boom") }) | |
| @@ -90,6 +94,7 @@ func TestFanoutFailureIsNotKept(t *testing.T) { | |||
| 90 | 94 | } | |
| 91 | 95 | ||
| 92 | 96 | func TestFanoutWaiterStopsWithItsContext(t *testing.T) { | |
| 97 | + | t.Parallel() | |
| 93 | 98 | var f fanout | |
| 94 | 99 | key := fanKey{srv: &Server{}, repo: "x", event: "b-1"} | |
| 95 | 100 | started, release := make(chan struct{}), make(chan struct{}) | |
| @@ -109,6 +114,7 @@ func TestFanoutWaiterStopsWithItsContext(t *testing.T) { | |||
| 109 | 114 | } | |
| 110 | 115 | ||
| 111 | 116 | func TestFanoutIsBounded(t *testing.T) { | |
| 117 | + | t.Parallel() | |
| 112 | 118 | var f fanout | |
| 113 | 119 | srv := &Server{} | |
| 114 | 120 | for i := range fanMax + 10 { | |
| @@ -202,6 +208,7 @@ func TestLiveRefsSharesAPushBetweenWatchers(t *testing.T) { | |||
| 202 | 208 | ||
| 203 | 209 | // The refs are asked in one page of up to maxRefs. | |
| 204 | 210 | func TestLoadRepoAsksTheRefsInOnePage(t *testing.T) { | |
| 211 | + | t.Parallel() | |
| 205 | 212 | api := &refsAPI{} | |
| 206 | 213 | s := refsServer(t, api) | |
| 207 | 214 | d, err := s.loadRepo(context.Background(), "x") | |
| @@ -22,6 +22,7 @@ import ( | |||||
| 22 | func frag(s string) []fragment { return []fragment{{event: "refs-list", html: s}} } | 22 | func frag(s string) []fragment { return []fragment{{event: "refs-list", html: s}} } | ||
| 23 | 23 | ||||
| 24 | func TestFanoutBuildsOncePerKey(t *testing.T) { | 24 | func TestFanoutBuildsOncePerKey(t *testing.T) { | ||
| 25 | + | t.Parallel() | |||
| 25 | srvA, srvB := &Server{}, &Server{} | 26 | srvA, srvB := &Server{}, &Server{} | ||
| 26 | base := fanKey{srv: srvA, repo: "x", event: "b-1"} | 27 | base := fanKey{srv: srvA, repo: "x", event: "b-1"} | ||
| 27 | for _, tt := range []struct { | 28 | for _, tt := range []struct { | ||
| @@ -37,6 +38,7 @@ func TestFanoutBuildsOncePerKey(t *testing.T) { | |||||
| 37 | {"no event id: nothing to share by", []fanKey{{srv: srvA, repo: "x"}, {srv: srvA, repo: "x"}}, 2}, | 38 | {"no event id: nothing to share by", []fanKey{{srv: srvA, repo: "x"}, {srv: srvA, repo: "x"}}, 2}, | ||
| 38 | } { | 39 | } { | ||
| 39 | t.Run(tt.name, func(t *testing.T) { | 40 | t.Run(tt.name, func(t *testing.T) { | ||
| 41 | + | t.Parallel() | |||
| 40 | var f fanout | 42 | var f fanout | ||
| 41 | var built atomic.Int32 | 43 | var built atomic.Int32 | ||
| 42 | for _, k := range tt.keys { | 44 | for _, k := range tt.keys { | ||
| @@ -54,6 +56,7 @@ func TestFanoutBuildsOncePerKey(t *testing.T) { | |||||
| 54 | } | 56 | } | ||
| 55 | 57 | ||||
| 56 | func TestFanoutConcurrentWatchersShareOneBuild(t *testing.T) { | 58 | func TestFanoutConcurrentWatchersShareOneBuild(t *testing.T) { | ||
| 59 | + | t.Parallel() | |||
| 57 | var f fanout | 60 | var f fanout | ||
| 58 | var built atomic.Int32 | 61 | var built atomic.Int32 | ||
| 59 | release := make(chan struct{}) | 62 | release := make(chan struct{}) | ||
| @@ -80,6 +83,7 @@ func TestFanoutConcurrentWatchersShareOneBuild(t *testing.T) { | |||||
| 80 | } | 83 | } | ||
| 81 | 84 | ||||
| 82 | func TestFanoutFailureIsNotKept(t *testing.T) { | 85 | func TestFanoutFailureIsNotKept(t *testing.T) { | ||
| 86 | + | t.Parallel() | |||
| 83 | var f fanout | 87 | var f fanout | ||
| 84 | key := fanKey{srv: &Server{}, repo: "x", event: "b-1"} | 88 | key := fanKey{srv: &Server{}, repo: "x", event: "b-1"} | ||
| 85 | _, err := f.do(context.Background(), key, func() ([]fragment, error) { return nil, errors.New("boom") }) | 89 | _, err := f.do(context.Background(), key, func() ([]fragment, error) { return nil, errors.New("boom") }) | ||
| @@ -90,6 +94,7 @@ func TestFanoutFailureIsNotKept(t *testing.T) { | |||||
| 90 | } | 94 | } | ||
| 91 | 95 | ||||
| 92 | func TestFanoutWaiterStopsWithItsContext(t *testing.T) { | 96 | func TestFanoutWaiterStopsWithItsContext(t *testing.T) { | ||
| 97 | + | t.Parallel() | |||
| 93 | var f fanout | 98 | var f fanout | ||
| 94 | key := fanKey{srv: &Server{}, repo: "x", event: "b-1"} | 99 | key := fanKey{srv: &Server{}, repo: "x", event: "b-1"} | ||
| 95 | started, release := make(chan struct{}), make(chan struct{}) | 100 | started, release := make(chan struct{}), make(chan struct{}) | ||
| @@ -109,6 +114,7 @@ func TestFanoutWaiterStopsWithItsContext(t *testing.T) { | |||||
| 109 | } | 114 | } | ||
| 110 | 115 | ||||
| 111 | func TestFanoutIsBounded(t *testing.T) { | 116 | func TestFanoutIsBounded(t *testing.T) { | ||
| 117 | + | t.Parallel() | |||
| 112 | var f fanout | 118 | var f fanout | ||
| 113 | srv := &Server{} | 119 | srv := &Server{} | ||
| 114 | for i := range fanMax + 10 { | 120 | for i := range fanMax + 10 { | ||
| @@ -202,6 +208,7 @@ func TestLiveRefsSharesAPushBetweenWatchers(t *testing.T) { | |||||
| 202 | 208 | ||||
| 203 | // The refs are asked in one page of up to maxRefs. | 209 | // The refs are asked in one page of up to maxRefs. | ||
| 204 | func TestLoadRepoAsksTheRefsInOnePage(t *testing.T) { | 210 | func TestLoadRepoAsksTheRefsInOnePage(t *testing.T) { | ||
| 211 | + | t.Parallel() | |||
| 205 | api := &refsAPI{} | 212 | api := &refsAPI{} | ||
| 206 | s := refsServer(t, api) | 213 | s := refsServer(t, api) | ||
| 207 | d, err := s.loadRepo(context.Background(), "x") | 214 | d, err := s.loadRepo(context.Background(), "x") | ||
M internal/adapters/web/fragments_test.go +4 -0
| @@ -24,6 +24,7 @@ func renderToString(t *testing.T, c templ.Component) string { | |||
| 24 | 24 | // (same id, role and marker), so that ermiteLive can replace the children and | |
| 25 | 25 | // leave the region where a screen reader watches it. | |
| 26 | 26 | func TestLiveSlotsAreStableStatusRegions(t *testing.T) { | |
| 27 | + | t.Parallel() | |
| 27 | 28 | for _, tt := range []struct { | |
| 28 | 29 | name string | |
| 29 | 30 | c templ.Component | |
| @@ -36,6 +37,7 @@ func TestLiveSlotsAreStableStatusRegions(t *testing.T) { | |||
| 36 | 37 | {"mr notice", MRNotice("a comment was added — reload", "/x/merge-requests/1"), "mr-notice", "a comment was added — reload"}, | |
| 37 | 38 | } { | |
| 38 | 39 | t.Run(tt.name, func(t *testing.T) { | |
| 40 | + | t.Parallel() | |
| 39 | 41 | got := renderToString(t, tt.c) | |
| 40 | 42 | assert.True(t, strings.HasPrefix(got, `<div id="`+tt.id+`" class="live-slot" role="status" data-slot>`), got) | |
| 41 | 43 | if tt.text == "" { | |
| @@ -51,6 +53,7 @@ func TestLiveSlotsAreStableStatusRegions(t *testing.T) { | |||
| 51 | 53 | // The header's refs button is a link to the refs page, which the script turns | |
| 52 | 54 | // into the dialog; the filters and the buttons that need scripts are marked. | |
| 53 | 55 | func TestShellDegradesWithoutScripts(t *testing.T) { | |
| 56 | + | t.Parallel() | |
| 54 | 57 | l := Layout{Repo: "demo", Ref: "main", Short: "abc1234", Assets: Assets{}} | |
| 55 | 58 | got := renderToString(t, Header(l)) | |
| 56 | 59 | assert.Contains(t, got, `<a class="btn ref" href="/demo/refs" data-fire="refs"`) | |
| @@ -63,6 +66,7 @@ func TestShellDegradesWithoutScripts(t *testing.T) { | |||
| 63 | 66 | // Data reaches the scripts through data-* attributes, never spliced into an | |
| 64 | 67 | // attribute that is evaluated. | |
| 65 | 68 | func TestDiffControlsPassDataThroughAttributes(t *testing.T) { | |
| 69 | + | t.Parallel() | |
| 66 | 70 | files := renderToString(t, FileList([]DiffFile{{ID: "f1'x", Path: "a.go", Status: "M"}})) | |
| 67 | 71 | assert.NotContains(t, files, ":aria-current", "the file in view is marked by ermite.js") | |
| 68 | 72 | assert.NotContains(t, files, "f1'x'", "no id is spliced into a script") | |
| @@ -24,6 +24,7 @@ func renderToString(t *testing.T, c templ.Component) string { | |||||
| 24 | // (same id, role and marker), so that ermiteLive can replace the children and | 24 | // (same id, role and marker), so that ermiteLive can replace the children and | ||
| 25 | // leave the region where a screen reader watches it. | 25 | // leave the region where a screen reader watches it. | ||
| 26 | func TestLiveSlotsAreStableStatusRegions(t *testing.T) { | 26 | func TestLiveSlotsAreStableStatusRegions(t *testing.T) { | ||
| 27 | + | t.Parallel() | |||
| 27 | for _, tt := range []struct { | 28 | for _, tt := range []struct { | ||
| 28 | name string | 29 | name string | ||
| 29 | c templ.Component | 30 | c templ.Component | ||
| @@ -36,6 +37,7 @@ func TestLiveSlotsAreStableStatusRegions(t *testing.T) { | |||||
| 36 | {"mr notice", MRNotice("a comment was added — reload", "/x/merge-requests/1"), "mr-notice", "a comment was added — reload"}, | 37 | {"mr notice", MRNotice("a comment was added — reload", "/x/merge-requests/1"), "mr-notice", "a comment was added — reload"}, | ||
| 37 | } { | 38 | } { | ||
| 38 | t.Run(tt.name, func(t *testing.T) { | 39 | t.Run(tt.name, func(t *testing.T) { | ||
| 40 | + | t.Parallel() | |||
| 39 | got := renderToString(t, tt.c) | 41 | got := renderToString(t, tt.c) | ||
| 40 | assert.True(t, strings.HasPrefix(got, `<div id="`+tt.id+`" class="live-slot" role="status" data-slot>`), got) | 42 | assert.True(t, strings.HasPrefix(got, `<div id="`+tt.id+`" class="live-slot" role="status" data-slot>`), got) | ||
| 41 | if tt.text == "" { | 43 | if tt.text == "" { | ||
| @@ -51,6 +53,7 @@ func TestLiveSlotsAreStableStatusRegions(t *testing.T) { | |||||
| 51 | // The header's refs button is a link to the refs page, which the script turns | 53 | // The header's refs button is a link to the refs page, which the script turns | ||
| 52 | // into the dialog; the filters and the buttons that need scripts are marked. | 54 | // into the dialog; the filters and the buttons that need scripts are marked. | ||
| 53 | func TestShellDegradesWithoutScripts(t *testing.T) { | 55 | func TestShellDegradesWithoutScripts(t *testing.T) { | ||
| 56 | + | t.Parallel() | |||
| 54 | l := Layout{Repo: "demo", Ref: "main", Short: "abc1234", Assets: Assets{}} | 57 | l := Layout{Repo: "demo", Ref: "main", Short: "abc1234", Assets: Assets{}} | ||
| 55 | got := renderToString(t, Header(l)) | 58 | got := renderToString(t, Header(l)) | ||
| 56 | assert.Contains(t, got, `<a class="btn ref" href="/demo/refs" data-fire="refs"`) | 59 | assert.Contains(t, got, `<a class="btn ref" href="/demo/refs" data-fire="refs"`) | ||
| @@ -63,6 +66,7 @@ func TestShellDegradesWithoutScripts(t *testing.T) { | |||||
| 63 | // Data reaches the scripts through data-* attributes, never spliced into an | 66 | // Data reaches the scripts through data-* attributes, never spliced into an | ||
| 64 | // attribute that is evaluated. | 67 | // attribute that is evaluated. | ||
| 65 | func TestDiffControlsPassDataThroughAttributes(t *testing.T) { | 68 | func TestDiffControlsPassDataThroughAttributes(t *testing.T) { | ||
| 69 | + | t.Parallel() | |||
| 66 | files := renderToString(t, FileList([]DiffFile{{ID: "f1'x", Path: "a.go", Status: "M"}})) | 70 | files := renderToString(t, FileList([]DiffFile{{ID: "f1'x", Path: "a.go", Status: "M"}})) | ||
| 67 | assert.NotContains(t, files, ":aria-current", "the file in view is marked by ermite.js") | 71 | assert.NotContains(t, files, ":aria-current", "the file in view is marked by ermite.js") | ||
| 68 | assert.NotContains(t, files, "f1'x'", "no id is spliced into a script") | 72 | assert.NotContains(t, files, "f1'x'", "no id is spliced into a script") | ||
M internal/adapters/web/highlight_test.go +12 -0
| @@ -15,6 +15,7 @@ import ( | |||
| 15 | 15 | ) | |
| 16 | 16 | ||
| 17 | 17 | func TestHighlightLines(t *testing.T) { | |
| 18 | + | t.Parallel() | |
| 18 | 19 | v := highlight(context.Background(), "a.go", "package main\n\nfunc main() {}\n") | |
| 19 | 20 | assert.Equal(t, 3, v.lines) | |
| 20 | 21 | assert.Equal(t, "Go", v.lang) | |
| @@ -30,6 +31,7 @@ func TestHighlightLines(t *testing.T) { | |||
| 30 | 31 | } | |
| 31 | 32 | ||
| 32 | 33 | func TestHighlightFallbackIsPlainAndEscaped(t *testing.T) { | |
| 34 | + | t.Parallel() | |
| 33 | 35 | v := highlight(context.Background(), "unknown.zzz", "<script>alert(1)</script> & \"q\"\n") | |
| 34 | 36 | assert.Equal(t, "", v.lang) | |
| 35 | 37 | h := string(v.html) | |
| @@ -41,6 +43,7 @@ func TestHighlightFallbackIsPlainAndEscaped(t *testing.T) { | |||
| 41 | 43 | } | |
| 42 | 44 | ||
| 43 | 45 | func TestHighlightMarksInvisibleCharacters(t *testing.T) { | |
| 46 | + | t.Parallel() | |
| 44 | 47 | for r, cp := range map[rune]string{ | |
| 45 | 48 | 0x202E: "U+202E", 0x2066: "U+2066", 0x2069: "U+2069", 0x200B: "U+200B", 0xFEFF: "U+FEFF", | |
| 46 | 49 | 0x0000: "U+0000", 0x001B: "U+001B", 0x2028: "U+2028", 0x3164: "U+3164", 0x1F: "U+001F", | |
| @@ -63,6 +66,7 @@ func TestHighlightMarksInvisibleCharacters(t *testing.T) { | |||
| 63 | 66 | // The lexers do not lose or add characters: the text of the lines is the | |
| 64 | 67 | // source, whatever the language. | |
| 65 | 68 | func TestHighlightKeepsTheSource(t *testing.T) { | |
| 69 | + | t.Parallel() | |
| 66 | 70 | srcs := map[string]string{ | |
| 67 | 71 | "a.go": "package a\n\nvar s = `multi\nline`\n// end", | |
| 68 | 72 | "b.py": "def f():\n '''doc\n string'''\n return 1\n", | |
| @@ -93,6 +97,7 @@ func TestHighlightKeepsTheSource(t *testing.T) { | |||
| 93 | 97 | } | |
| 94 | 98 | ||
| 95 | 99 | func TestFirstLinesAndCount(t *testing.T) { | |
| 100 | + | t.Parallel() | |
| 96 | 101 | assert.Equal(t, "a\nb\n", firstLines("a\nb\nc\n", 2)) | |
| 97 | 102 | assert.Equal(t, "a\nb", firstLines("a\nb", 5)) | |
| 98 | 103 | assert.Equal(t, 3, countLines([]byte("a\nb\nc"))) | |
| @@ -101,6 +106,7 @@ func TestFirstLinesAndCount(t *testing.T) { | |||
| 101 | 106 | } | |
| 102 | 107 | ||
| 103 | 108 | func TestHighlightCtxCancelled(t *testing.T) { | |
| 109 | + | t.Parallel() | |
| 104 | 110 | ctx, cancel := context.WithCancel(context.Background()) | |
| 105 | 111 | cancel() | |
| 106 | 112 | v := highlightCtx(ctx, "a.go", "package a\n") | |
| @@ -116,12 +122,14 @@ func (panicLexer) Tokenise(*chroma.TokeniseOptions, string) (chroma.Iterator, er | |||
| 116 | 122 | } | |
| 117 | 123 | ||
| 118 | 124 | func TestTokeniseRecoversFromALexerPanic(t *testing.T) { | |
| 125 | + | t.Parallel() | |
| 119 | 126 | toks, ok := tokenise(context.Background(), panicLexer{}, "x\n") | |
| 120 | 127 | assert.False(t, ok) | |
| 121 | 128 | assert.Nil(t, toks) | |
| 122 | 129 | } | |
| 123 | 130 | ||
| 124 | 131 | func TestSniffImage(t *testing.T) { | |
| 132 | + | t.Parallel() | |
| 125 | 133 | assert.Equal(t, "image/png", sniffImage([]byte("\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR"))) | |
| 126 | 134 | assert.Equal(t, "image/gif", sniffImage([]byte("GIF89a...."))) | |
| 127 | 135 | assert.Equal(t, "image/jpeg", sniffImage([]byte("\xff\xd8\xff\xe0\x00\x10JFIF"))) | |
| @@ -132,6 +140,7 @@ func TestSniffImage(t *testing.T) { | |||
| 132 | 140 | } | |
| 133 | 141 | ||
| 134 | 142 | func TestLocate(t *testing.T) { | |
| 143 | + | t.Parallel() | |
| 135 | 144 | b, tag := pb.RefKind_REF_KIND_BRANCH, pb.RefKind_REF_KIND_TAG | |
| 136 | 145 | d := &repoData{ | |
| 137 | 146 | branches: []*pb.Ref{ref(b, "main"), ref(b, "feature/x")}, | |
| @@ -169,6 +178,7 @@ func TestLocate(t *testing.T) { | |||
| 169 | 178 | } | |
| 170 | 179 | ||
| 171 | 180 | func TestFileCrumbs(t *testing.T) { | |
| 181 | + | t.Parallel() | |
| 172 | 182 | cs := pathCrumbs("demo", "blob", target{name: "feature/x", path: "src/a.go"}) | |
| 173 | 183 | require.Len(t, cs, 5) | |
| 174 | 184 | assert.Equal(t, "/demo/tree/feature/x/src", cs[3].Href) | |
| @@ -220,6 +230,7 @@ func normSpace(s string) string { | |||
| 220 | 230 | } | |
| 221 | 231 | ||
| 222 | 232 | func TestTokensMatch(t *testing.T) { | |
| 233 | + | t.Parallel() | |
| 223 | 234 | toks := func(vs ...string) []chroma.Token { | |
| 224 | 235 | var out []chroma.Token | |
| 225 | 236 | for _, v := range vs { | |
| @@ -247,6 +258,7 @@ func TestTokensMatch(t *testing.T) { | |||
| 247 | 258 | {"unicode", toks("é", "日本"), "é日本", true}, | |
| 248 | 259 | } { | |
| 249 | 260 | t.Run(tt.name, func(t *testing.T) { | |
| 261 | + | t.Parallel() | |
| 250 | 262 | assert.Equal(t, tt.want, tokensMatch(tt.toks, tt.src)) | |
| 251 | 263 | }) | |
| 252 | 264 | } | |
| @@ -15,6 +15,7 @@ import ( | |||||
| 15 | ) | 15 | ) | ||
| 16 | 16 | ||||
| 17 | func TestHighlightLines(t *testing.T) { | 17 | func TestHighlightLines(t *testing.T) { | ||
| 18 | + | t.Parallel() | |||
| 18 | v := highlight(context.Background(), "a.go", "package main\n\nfunc main() {}\n") | 19 | v := highlight(context.Background(), "a.go", "package main\n\nfunc main() {}\n") | ||
| 19 | assert.Equal(t, 3, v.lines) | 20 | assert.Equal(t, 3, v.lines) | ||
| 20 | assert.Equal(t, "Go", v.lang) | 21 | assert.Equal(t, "Go", v.lang) | ||
| @@ -30,6 +31,7 @@ func TestHighlightLines(t *testing.T) { | |||||
| 30 | } | 31 | } | ||
| 31 | 32 | ||||
| 32 | func TestHighlightFallbackIsPlainAndEscaped(t *testing.T) { | 33 | func TestHighlightFallbackIsPlainAndEscaped(t *testing.T) { | ||
| 34 | + | t.Parallel() | |||
| 33 | v := highlight(context.Background(), "unknown.zzz", "<script>alert(1)</script> & \"q\"\n") | 35 | v := highlight(context.Background(), "unknown.zzz", "<script>alert(1)</script> & \"q\"\n") | ||
| 34 | assert.Equal(t, "", v.lang) | 36 | assert.Equal(t, "", v.lang) | ||
| 35 | h := string(v.html) | 37 | h := string(v.html) | ||
| @@ -41,6 +43,7 @@ func TestHighlightFallbackIsPlainAndEscaped(t *testing.T) { | |||||
| 41 | } | 43 | } | ||
| 42 | 44 | ||||
| 43 | func TestHighlightMarksInvisibleCharacters(t *testing.T) { | 45 | func TestHighlightMarksInvisibleCharacters(t *testing.T) { | ||
| 46 | + | t.Parallel() | |||
| 44 | for r, cp := range map[rune]string{ | 47 | for r, cp := range map[rune]string{ | ||
| 45 | 0x202E: "U+202E", 0x2066: "U+2066", 0x2069: "U+2069", 0x200B: "U+200B", 0xFEFF: "U+FEFF", | 48 | 0x202E: "U+202E", 0x2066: "U+2066", 0x2069: "U+2069", 0x200B: "U+200B", 0xFEFF: "U+FEFF", | ||
| 46 | 0x0000: "U+0000", 0x001B: "U+001B", 0x2028: "U+2028", 0x3164: "U+3164", 0x1F: "U+001F", | 49 | 0x0000: "U+0000", 0x001B: "U+001B", 0x2028: "U+2028", 0x3164: "U+3164", 0x1F: "U+001F", | ||
| @@ -63,6 +66,7 @@ func TestHighlightMarksInvisibleCharacters(t *testing.T) { | |||||
| 63 | // The lexers do not lose or add characters: the text of the lines is the | 66 | // The lexers do not lose or add characters: the text of the lines is the | ||
| 64 | // source, whatever the language. | 67 | // source, whatever the language. | ||
| 65 | func TestHighlightKeepsTheSource(t *testing.T) { | 68 | func TestHighlightKeepsTheSource(t *testing.T) { | ||
| 69 | + | t.Parallel() | |||
| 66 | srcs := map[string]string{ | 70 | srcs := map[string]string{ | ||
| 67 | "a.go": "package a\n\nvar s = `multi\nline`\n// end", | 71 | "a.go": "package a\n\nvar s = `multi\nline`\n// end", | ||
| 68 | "b.py": "def f():\n '''doc\n string'''\n return 1\n", | 72 | "b.py": "def f():\n '''doc\n string'''\n return 1\n", | ||
| @@ -93,6 +97,7 @@ func TestHighlightKeepsTheSource(t *testing.T) { | |||||
| 93 | } | 97 | } | ||
| 94 | 98 | ||||
| 95 | func TestFirstLinesAndCount(t *testing.T) { | 99 | func TestFirstLinesAndCount(t *testing.T) { | ||
| 100 | + | t.Parallel() | |||
| 96 | assert.Equal(t, "a\nb\n", firstLines("a\nb\nc\n", 2)) | 101 | assert.Equal(t, "a\nb\n", firstLines("a\nb\nc\n", 2)) | ||
| 97 | assert.Equal(t, "a\nb", firstLines("a\nb", 5)) | 102 | assert.Equal(t, "a\nb", firstLines("a\nb", 5)) | ||
| 98 | assert.Equal(t, 3, countLines([]byte("a\nb\nc"))) | 103 | assert.Equal(t, 3, countLines([]byte("a\nb\nc"))) | ||
| @@ -101,6 +106,7 @@ func TestFirstLinesAndCount(t *testing.T) { | |||||
| 101 | } | 106 | } | ||
| 102 | 107 | ||||
| 103 | func TestHighlightCtxCancelled(t *testing.T) { | 108 | func TestHighlightCtxCancelled(t *testing.T) { | ||
| 109 | + | t.Parallel() | |||
| 104 | ctx, cancel := context.WithCancel(context.Background()) | 110 | ctx, cancel := context.WithCancel(context.Background()) | ||
| 105 | cancel() | 111 | cancel() | ||
| 106 | v := highlightCtx(ctx, "a.go", "package a\n") | 112 | v := highlightCtx(ctx, "a.go", "package a\n") | ||
| @@ -116,12 +122,14 @@ func (panicLexer) Tokenise(*chroma.TokeniseOptions, string) (chroma.Iterator, er | |||||
| 116 | } | 122 | } | ||
| 117 | 123 | ||||
| 118 | func TestTokeniseRecoversFromALexerPanic(t *testing.T) { | 124 | func TestTokeniseRecoversFromALexerPanic(t *testing.T) { | ||
| 125 | + | t.Parallel() | |||
| 119 | toks, ok := tokenise(context.Background(), panicLexer{}, "x\n") | 126 | toks, ok := tokenise(context.Background(), panicLexer{}, "x\n") | ||
| 120 | assert.False(t, ok) | 127 | assert.False(t, ok) | ||
| 121 | assert.Nil(t, toks) | 128 | assert.Nil(t, toks) | ||
| 122 | } | 129 | } | ||
| 123 | 130 | ||||
| 124 | func TestSniffImage(t *testing.T) { | 131 | func TestSniffImage(t *testing.T) { | ||
| 132 | + | t.Parallel() | |||
| 125 | assert.Equal(t, "image/png", sniffImage([]byte("\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR"))) | 133 | assert.Equal(t, "image/png", sniffImage([]byte("\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR"))) | ||
| 126 | assert.Equal(t, "image/gif", sniffImage([]byte("GIF89a...."))) | 134 | assert.Equal(t, "image/gif", sniffImage([]byte("GIF89a...."))) | ||
| 127 | assert.Equal(t, "image/jpeg", sniffImage([]byte("\xff\xd8\xff\xe0\x00\x10JFIF"))) | 135 | assert.Equal(t, "image/jpeg", sniffImage([]byte("\xff\xd8\xff\xe0\x00\x10JFIF"))) | ||
| @@ -132,6 +140,7 @@ func TestSniffImage(t *testing.T) { | |||||
| 132 | } | 140 | } | ||
| 133 | 141 | ||||
| 134 | func TestLocate(t *testing.T) { | 142 | func TestLocate(t *testing.T) { | ||
| 143 | + | t.Parallel() | |||
| 135 | b, tag := pb.RefKind_REF_KIND_BRANCH, pb.RefKind_REF_KIND_TAG | 144 | b, tag := pb.RefKind_REF_KIND_BRANCH, pb.RefKind_REF_KIND_TAG | ||
| 136 | d := &repoData{ | 145 | d := &repoData{ | ||
| 137 | branches: []*pb.Ref{ref(b, "main"), ref(b, "feature/x")}, | 146 | branches: []*pb.Ref{ref(b, "main"), ref(b, "feature/x")}, | ||
| @@ -169,6 +178,7 @@ func TestLocate(t *testing.T) { | |||||
| 169 | } | 178 | } | ||
| 170 | 179 | ||||
| 171 | func TestFileCrumbs(t *testing.T) { | 180 | func TestFileCrumbs(t *testing.T) { | ||
| 181 | + | t.Parallel() | |||
| 172 | cs := pathCrumbs("demo", "blob", target{name: "feature/x", path: "src/a.go"}) | 182 | cs := pathCrumbs("demo", "blob", target{name: "feature/x", path: "src/a.go"}) | ||
| 173 | require.Len(t, cs, 5) | 183 | require.Len(t, cs, 5) | ||
| 174 | assert.Equal(t, "/demo/tree/feature/x/src", cs[3].Href) | 184 | assert.Equal(t, "/demo/tree/feature/x/src", cs[3].Href) | ||
| @@ -220,6 +230,7 @@ func normSpace(s string) string { | |||||
| 220 | } | 230 | } | ||
| 221 | 231 | ||||
| 222 | func TestTokensMatch(t *testing.T) { | 232 | func TestTokensMatch(t *testing.T) { | ||
| 233 | + | t.Parallel() | |||
| 223 | toks := func(vs ...string) []chroma.Token { | 234 | toks := func(vs ...string) []chroma.Token { | ||
| 224 | var out []chroma.Token | 235 | var out []chroma.Token | ||
| 225 | for _, v := range vs { | 236 | for _, v := range vs { | ||
| @@ -247,6 +258,7 @@ func TestTokensMatch(t *testing.T) { | |||||
| 247 | {"unicode", toks("é", "日本"), "é日本", true}, | 258 | {"unicode", toks("é", "日本"), "é日本", true}, | ||
| 248 | } { | 259 | } { | ||
| 249 | t.Run(tt.name, func(t *testing.T) { | 260 | t.Run(tt.name, func(t *testing.T) { | ||
| 261 | + | t.Parallel() | |||
| 250 | assert.Equal(t, tt.want, tokensMatch(tt.toks, tt.src)) | 262 | assert.Equal(t, tt.want, tokensMatch(tt.toks, tt.src)) | ||
| 251 | }) | 263 | }) | ||
| 252 | } | 264 | } | ||
M internal/adapters/web/markdown_test.go +6 -0
| @@ -19,6 +19,7 @@ func renderMD(t *testing.T, src string) (string, []Heading) { | |||
| 19 | 19 | ||
| 20 | 20 | // AC4: raw HTML, javascript: links and SVG are neutralised. | |
| 21 | 21 | func TestMarkdownNeutralisation(t *testing.T) { | |
| 22 | + | t.Parallel() | |
| 22 | 23 | tests := []struct { | |
| 23 | 24 | name, src string | |
| 24 | 25 | absent []string | |
| @@ -59,6 +60,7 @@ func TestMarkdownNeutralisation(t *testing.T) { | |||
| 59 | 60 | } | |
| 60 | 61 | for _, tt := range tests { | |
| 61 | 62 | t.Run(tt.name, func(t *testing.T) { | |
| 63 | + | t.Parallel() | |
| 62 | 64 | out, _ := renderMD(t, tt.src) | |
| 63 | 65 | for _, s := range tt.absent { | |
| 64 | 66 | assert.NotContains(t, out, s) | |
| @@ -80,6 +82,7 @@ func TestMarkdownNeutralisation(t *testing.T) { | |||
| 80 | 82 | } | |
| 81 | 83 | ||
| 82 | 84 | func TestMarkdownLinks(t *testing.T) { | |
| 85 | + | t.Parallel() | |
| 83 | 86 | tests := []struct{ name, src, href string }{ | |
| 84 | 87 | {"relative from the file's directory", "[a](other.md)", `href="/demo/blob/main/docs/other.md"`}, | |
| 85 | 88 | {"parent", "[a](../LICENSE)", `href="/demo/blob/main/LICENSE"`}, | |
| @@ -95,6 +98,7 @@ func TestMarkdownLinks(t *testing.T) { | |||
| 95 | 98 | } | |
| 96 | 99 | for _, tt := range tests { | |
| 97 | 100 | t.Run(tt.name, func(t *testing.T) { | |
| 101 | + | t.Parallel() | |
| 98 | 102 | out, _ := renderMD(t, tt.src) | |
| 99 | 103 | if tt.href == "" { | |
| 100 | 104 | assert.NotContains(t, out, "<a ") | |
| @@ -106,6 +110,7 @@ func TestMarkdownLinks(t *testing.T) { | |||
| 106 | 110 | } | |
| 107 | 111 | ||
| 108 | 112 | func TestMarkdownOutline(t *testing.T) { | |
| 113 | + | t.Parallel() | |
| 109 | 114 | out, outline := renderMD(t, "# Title\n\n## Install `now`\n\n### Install `now`\n\ntext\n\n#### raw *em*\n") | |
| 110 | 115 | assert.Equal(t, []Heading{ | |
| 111 | 116 | {Level: 1, ID: "md-title", Text: "Title"}, | |
| @@ -124,6 +129,7 @@ func TestMarkdownOutline(t *testing.T) { | |||
| 124 | 129 | } | |
| 125 | 130 | ||
| 126 | 131 | func TestMarkdownGFM(t *testing.T) { | |
| 132 | + | t.Parallel() | |
| 127 | 133 | out, _ := renderMD(t, "| a | b |\n|:-|-:|\n| 1 | 2 |\n\n~~gone~~\n\n- [x] task\n") | |
| 128 | 134 | assert.Contains(t, out, `<th align="right">b</th>`, "alignment is an attribute, never a style") | |
| 129 | 135 | assert.NotContains(t, out, "style=") | |
| @@ -19,6 +19,7 @@ func renderMD(t *testing.T, src string) (string, []Heading) { | |||||
| 19 | 19 | ||||
| 20 | // AC4: raw HTML, javascript: links and SVG are neutralised. | 20 | // AC4: raw HTML, javascript: links and SVG are neutralised. | ||
| 21 | func TestMarkdownNeutralisation(t *testing.T) { | 21 | func TestMarkdownNeutralisation(t *testing.T) { | ||
| 22 | + | t.Parallel() | |||
| 22 | tests := []struct { | 23 | tests := []struct { | ||
| 23 | name, src string | 24 | name, src string | ||
| 24 | absent []string | 25 | absent []string | ||
| @@ -59,6 +60,7 @@ func TestMarkdownNeutralisation(t *testing.T) { | |||||
| 59 | } | 60 | } | ||
| 60 | for _, tt := range tests { | 61 | for _, tt := range tests { | ||
| 61 | t.Run(tt.name, func(t *testing.T) { | 62 | t.Run(tt.name, func(t *testing.T) { | ||
| 63 | + | t.Parallel() | |||
| 62 | out, _ := renderMD(t, tt.src) | 64 | out, _ := renderMD(t, tt.src) | ||
| 63 | for _, s := range tt.absent { | 65 | for _, s := range tt.absent { | ||
| 64 | assert.NotContains(t, out, s) | 66 | assert.NotContains(t, out, s) | ||
| @@ -80,6 +82,7 @@ func TestMarkdownNeutralisation(t *testing.T) { | |||||
| 80 | } | 82 | } | ||
| 81 | 83 | ||||
| 82 | func TestMarkdownLinks(t *testing.T) { | 84 | func TestMarkdownLinks(t *testing.T) { | ||
| 85 | + | t.Parallel() | |||
| 83 | tests := []struct{ name, src, href string }{ | 86 | tests := []struct{ name, src, href string }{ | ||
| 84 | {"relative from the file's directory", "[a](other.md)", `href="/demo/blob/main/docs/other.md"`}, | 87 | {"relative from the file's directory", "[a](other.md)", `href="/demo/blob/main/docs/other.md"`}, | ||
| 85 | {"parent", "[a](../LICENSE)", `href="/demo/blob/main/LICENSE"`}, | 88 | {"parent", "[a](../LICENSE)", `href="/demo/blob/main/LICENSE"`}, | ||
| @@ -95,6 +98,7 @@ func TestMarkdownLinks(t *testing.T) { | |||||
| 95 | } | 98 | } | ||
| 96 | for _, tt := range tests { | 99 | for _, tt := range tests { | ||
| 97 | t.Run(tt.name, func(t *testing.T) { | 100 | t.Run(tt.name, func(t *testing.T) { | ||
| 101 | + | t.Parallel() | |||
| 98 | out, _ := renderMD(t, tt.src) | 102 | out, _ := renderMD(t, tt.src) | ||
| 99 | if tt.href == "" { | 103 | if tt.href == "" { | ||
| 100 | assert.NotContains(t, out, "<a ") | 104 | assert.NotContains(t, out, "<a ") | ||
| @@ -106,6 +110,7 @@ func TestMarkdownLinks(t *testing.T) { | |||||
| 106 | } | 110 | } | ||
| 107 | 111 | ||||
| 108 | func TestMarkdownOutline(t *testing.T) { | 112 | func TestMarkdownOutline(t *testing.T) { | ||
| 113 | + | t.Parallel() | |||
| 109 | out, outline := renderMD(t, "# Title\n\n## Install `now`\n\n### Install `now`\n\ntext\n\n#### raw *em*\n") | 114 | out, outline := renderMD(t, "# Title\n\n## Install `now`\n\n### Install `now`\n\ntext\n\n#### raw *em*\n") | ||
| 110 | assert.Equal(t, []Heading{ | 115 | assert.Equal(t, []Heading{ | ||
| 111 | {Level: 1, ID: "md-title", Text: "Title"}, | 116 | {Level: 1, ID: "md-title", Text: "Title"}, | ||
| @@ -124,6 +129,7 @@ func TestMarkdownOutline(t *testing.T) { | |||||
| 124 | } | 129 | } | ||
| 125 | 130 | ||||
| 126 | func TestMarkdownGFM(t *testing.T) { | 131 | func TestMarkdownGFM(t *testing.T) { | ||
| 132 | + | t.Parallel() | |||
| 127 | out, _ := renderMD(t, "| a | b |\n|:-|-:|\n| 1 | 2 |\n\n~~gone~~\n\n- [x] task\n") | 133 | out, _ := renderMD(t, "| a | b |\n|:-|-:|\n| 1 | 2 |\n\n~~gone~~\n\n- [x] task\n") | ||
| 128 | assert.Contains(t, out, `<th align="right">b</th>`, "alignment is an attribute, never a style") | 134 | assert.Contains(t, out, `<th align="right">b</th>`, "alignment is an attribute, never a style") | ||
| 129 | assert.NotContains(t, out, "style=") | 135 | assert.NotContains(t, out, "style=") | ||
M internal/adapters/web/plaintext_test.go +6 -0
| @@ -14,6 +14,7 @@ import ( | |||
| 14 | 14 | // Plain text stays text; only http(s) addresses become links, without the | |
| 15 | 15 | // punctuation that ends a sentence; invisible characters are marked. | |
| 16 | 16 | func TestPlainParts(t *testing.T) { | |
| 17 | + | t.Parallel() | |
| 17 | 18 | tests := []struct { | |
| 18 | 19 | name, in string | |
| 19 | 20 | want []TextPart | |
| @@ -32,6 +33,7 @@ func TestPlainParts(t *testing.T) { | |||
| 32 | 33 | } | |
| 33 | 34 | for _, tt := range tests { | |
| 34 | 35 | t.Run(tt.name, func(t *testing.T) { | |
| 36 | + | t.Parallel() | |
| 35 | 37 | assert.Equal(t, tt.want, plainParts(tt.in)) | |
| 36 | 38 | }) | |
| 37 | 39 | } | |
| @@ -39,12 +41,14 @@ func TestPlainParts(t *testing.T) { | |||
| 39 | 41 | ||
| 40 | 42 | // userText escapes the text and the address, and marks the link as untrusted. | |
| 41 | 43 | func TestUserTextEscapes(t *testing.T) { | |
| 44 | + | t.Parallel() | |
| 42 | 45 | var b bytes.Buffer | |
| 43 | 46 | require.NoError(t, userText(plainParts(`<b>x</b> & http://x.test/?a=1&b=2`)).Render(context.Background(), &b)) | |
| 44 | 47 | assert.Equal(t, `<b>x</b> & <a href="http://x.test/?a=1&b=2" rel="noopener nofollow ugc">http://x.test/?a=1&b=2</a>`, b.String()) | |
| 45 | 48 | } | |
| 46 | 49 | ||
| 47 | 50 | func TestParseOn(t *testing.T) { | |
| 51 | + | t.Parallel() | |
| 48 | 52 | assert.Equal(t, &lineRef{side: "new", line: 12, path: "a/b:c.go"}, parseOn("new:12:a/b:c.go")) | |
| 49 | 53 | assert.Equal(t, &lineRef{side: "old", line: 1, path: "x"}, parseOn("old:1:x")) | |
| 50 | 54 | for _, s := range []string{"", "new", "new:12", "new:12:", "new:0:x", "new:-1:x", "new:x:y", "up:1:x", "new::x"} { | |
| @@ -53,6 +57,7 @@ func TestParseOn(t *testing.T) { | |||
| 53 | 57 | } | |
| 54 | 58 | ||
| 55 | 59 | func TestShellWord(t *testing.T) { | |
| 60 | + | t.Parallel() | |
| 56 | 61 | assert.Equal(t, "feat/search", shellWord("feat/search")) | |
| 57 | 62 | assert.Equal(t, `'a b'`, shellWord("a b")) | |
| 58 | 63 | assert.Equal(t, `'it'\''s'`, shellWord("it's")) | |
| @@ -61,6 +66,7 @@ func TestShellWord(t *testing.T) { | |||
| 61 | 66 | ||
| 62 | 67 | // The notes of a split row: a context line counts once. | |
| 63 | 68 | func TestSplitRowNotes(t *testing.T) { | |
| 69 | + | t.Parallel() | |
| 64 | 70 | a, b := CommentView{ID: "1"}, CommentView{ID: "2"} | |
| 65 | 71 | ctx := &DiffLine{Notes: []CommentView{a}} | |
| 66 | 72 | assert.Equal(t, []CommentView{a}, SplitRow{ctx, ctx}.Notes()) | |
| @@ -14,6 +14,7 @@ import ( | |||||
| 14 | // Plain text stays text; only http(s) addresses become links, without the | 14 | // Plain text stays text; only http(s) addresses become links, without the | ||
| 15 | // punctuation that ends a sentence; invisible characters are marked. | 15 | // punctuation that ends a sentence; invisible characters are marked. | ||
| 16 | func TestPlainParts(t *testing.T) { | 16 | func TestPlainParts(t *testing.T) { | ||
| 17 | + | t.Parallel() | |||
| 17 | tests := []struct { | 18 | tests := []struct { | ||
| 18 | name, in string | 19 | name, in string | ||
| 19 | want []TextPart | 20 | want []TextPart | ||
| @@ -32,6 +33,7 @@ func TestPlainParts(t *testing.T) { | |||||
| 32 | } | 33 | } | ||
| 33 | for _, tt := range tests { | 34 | for _, tt := range tests { | ||
| 34 | t.Run(tt.name, func(t *testing.T) { | 35 | t.Run(tt.name, func(t *testing.T) { | ||
| 36 | + | t.Parallel() | |||
| 35 | assert.Equal(t, tt.want, plainParts(tt.in)) | 37 | assert.Equal(t, tt.want, plainParts(tt.in)) | ||
| 36 | }) | 38 | }) | ||
| 37 | } | 39 | } | ||
| @@ -39,12 +41,14 @@ func TestPlainParts(t *testing.T) { | |||||
| 39 | 41 | ||||
| 40 | // userText escapes the text and the address, and marks the link as untrusted. | 42 | // userText escapes the text and the address, and marks the link as untrusted. | ||
| 41 | func TestUserTextEscapes(t *testing.T) { | 43 | func TestUserTextEscapes(t *testing.T) { | ||
| 44 | + | t.Parallel() | |||
| 42 | var b bytes.Buffer | 45 | var b bytes.Buffer | ||
| 43 | require.NoError(t, userText(plainParts(`<b>x</b> & http://x.test/?a=1&b=2`)).Render(context.Background(), &b)) | 46 | require.NoError(t, userText(plainParts(`<b>x</b> & http://x.test/?a=1&b=2`)).Render(context.Background(), &b)) | ||
| 44 | assert.Equal(t, `<b>x</b> & <a href="http://x.test/?a=1&b=2" rel="noopener nofollow ugc">http://x.test/?a=1&b=2</a>`, b.String()) | 47 | assert.Equal(t, `<b>x</b> & <a href="http://x.test/?a=1&b=2" rel="noopener nofollow ugc">http://x.test/?a=1&b=2</a>`, b.String()) | ||
| 45 | } | 48 | } | ||
| 46 | 49 | ||||
| 47 | func TestParseOn(t *testing.T) { | 50 | func TestParseOn(t *testing.T) { | ||
| 51 | + | t.Parallel() | |||
| 48 | assert.Equal(t, &lineRef{side: "new", line: 12, path: "a/b:c.go"}, parseOn("new:12:a/b:c.go")) | 52 | assert.Equal(t, &lineRef{side: "new", line: 12, path: "a/b:c.go"}, parseOn("new:12:a/b:c.go")) | ||
| 49 | assert.Equal(t, &lineRef{side: "old", line: 1, path: "x"}, parseOn("old:1:x")) | 53 | assert.Equal(t, &lineRef{side: "old", line: 1, path: "x"}, parseOn("old:1:x")) | ||
| 50 | for _, s := range []string{"", "new", "new:12", "new:12:", "new:0:x", "new:-1:x", "new:x:y", "up:1:x", "new::x"} { | 54 | for _, s := range []string{"", "new", "new:12", "new:12:", "new:0:x", "new:-1:x", "new:x:y", "up:1:x", "new::x"} { | ||
| @@ -53,6 +57,7 @@ func TestParseOn(t *testing.T) { | |||||
| 53 | } | 57 | } | ||
| 54 | 58 | ||||
| 55 | func TestShellWord(t *testing.T) { | 59 | func TestShellWord(t *testing.T) { | ||
| 60 | + | t.Parallel() | |||
| 56 | assert.Equal(t, "feat/search", shellWord("feat/search")) | 61 | assert.Equal(t, "feat/search", shellWord("feat/search")) | ||
| 57 | assert.Equal(t, `'a b'`, shellWord("a b")) | 62 | assert.Equal(t, `'a b'`, shellWord("a b")) | ||
| 58 | assert.Equal(t, `'it'\''s'`, shellWord("it's")) | 63 | assert.Equal(t, `'it'\''s'`, shellWord("it's")) | ||
| @@ -61,6 +66,7 @@ func TestShellWord(t *testing.T) { | |||||
| 61 | 66 | ||||
| 62 | // The notes of a split row: a context line counts once. | 67 | // The notes of a split row: a context line counts once. | ||
| 63 | func TestSplitRowNotes(t *testing.T) { | 68 | func TestSplitRowNotes(t *testing.T) { | ||
| 69 | + | t.Parallel() | |||
| 64 | a, b := CommentView{ID: "1"}, CommentView{ID: "2"} | 70 | a, b := CommentView{ID: "1"}, CommentView{ID: "2"} | ||
| 65 | ctx := &DiffLine{Notes: []CommentView{a}} | 71 | ctx := &DiffLine{Notes: []CommentView{a}} | ||
| 66 | assert.Equal(t, []CommentView{a}, SplitRow{ctx, ctx}.Notes()) | 72 | assert.Equal(t, []CommentView{a}, SplitRow{ctx, ctx}.Notes()) | ||
M internal/adapters/web/raw_test.go +2 -0
| @@ -22,6 +22,7 @@ const blobID = "0123456789abcdef0123456789abcdef01234567" | |||
| 22 | 22 | // A revalidation of an unchanged file is answered from the tree entry, before | |
| 23 | 23 | // the content is opened; anything else opens the file. | |
| 24 | 24 | func TestRawChecksETagBeforeOpeningTheBlob(t *testing.T) { | |
| 25 | + | t.Parallel() | |
| 25 | 26 | entry := func(kind pb.EntryKind, id string) *pb.TreeEntry { | |
| 26 | 27 | return &pb.TreeEntry{Name: "big.bin", Path: "docs/big.bin", Kind: kind, Id: id} | |
| 27 | 28 | } | |
| @@ -82,6 +83,7 @@ func TestRawChecksETagBeforeOpeningTheBlob(t *testing.T) { | |||
| 82 | 83 | } | |
| 83 | 84 | for _, tt := range tests { | |
| 84 | 85 | t.Run(tt.name, func(t *testing.T) { | |
| 86 | + | t.Parallel() | |
| 85 | 87 | repos := mocks.NewRepoServiceClient(t) | |
| 86 | 88 | git := mocks.NewGitServiceClient(t) | |
| 87 | 89 | repos.EXPECT().GetRepo(mock.Anything, mock.Anything). | |
| @@ -22,6 +22,7 @@ const blobID = "0123456789abcdef0123456789abcdef01234567" | |||||
| 22 | // A revalidation of an unchanged file is answered from the tree entry, before | 22 | // A revalidation of an unchanged file is answered from the tree entry, before | ||
| 23 | // the content is opened; anything else opens the file. | 23 | // the content is opened; anything else opens the file. | ||
| 24 | func TestRawChecksETagBeforeOpeningTheBlob(t *testing.T) { | 24 | func TestRawChecksETagBeforeOpeningTheBlob(t *testing.T) { | ||
| 25 | + | t.Parallel() | |||
| 25 | entry := func(kind pb.EntryKind, id string) *pb.TreeEntry { | 26 | entry := func(kind pb.EntryKind, id string) *pb.TreeEntry { | ||
| 26 | return &pb.TreeEntry{Name: "big.bin", Path: "docs/big.bin", Kind: kind, Id: id} | 27 | return &pb.TreeEntry{Name: "big.bin", Path: "docs/big.bin", Kind: kind, Id: id} | ||
| 27 | } | 28 | } | ||
| @@ -82,6 +83,7 @@ func TestRawChecksETagBeforeOpeningTheBlob(t *testing.T) { | |||||
| 82 | } | 83 | } | ||
| 83 | for _, tt := range tests { | 84 | for _, tt := range tests { | ||
| 84 | t.Run(tt.name, func(t *testing.T) { | 85 | t.Run(tt.name, func(t *testing.T) { | ||
| 86 | + | t.Parallel() | |||
| 85 | repos := mocks.NewRepoServiceClient(t) | 87 | repos := mocks.NewRepoServiceClient(t) | ||
| 86 | git := mocks.NewGitServiceClient(t) | 88 | git := mocks.NewGitServiceClient(t) | ||
| 87 | repos.EXPECT().GetRepo(mock.Anything, mock.Anything). | 89 | repos.EXPECT().GetRepo(mock.Anything, mock.Anything). | ||
M internal/adapters/web/refs_test.go +3 -0
| @@ -16,6 +16,7 @@ func ref(kind pb.RefKind, short string) *pb.Ref { | |||
| 16 | 16 | ||
| 17 | 17 | // AC3: a ref with slashes is found by the longest matching prefix. | |
| 18 | 18 | func TestResolveRef(t *testing.T) { | |
| 19 | + | t.Parallel() | |
| 19 | 20 | b, tag := pb.RefKind_REF_KIND_BRANCH, pb.RefKind_REF_KIND_TAG | |
| 20 | 21 | refs := []*pb.Ref{ | |
| 21 | 22 | ref(b, "main"), ref(b, "feature"), ref(b, "feature/x"), ref(b, "feature/x/y"), ref(tag, "v1/rc"), | |
| @@ -39,6 +40,7 @@ func TestResolveRef(t *testing.T) { | |||
| 39 | 40 | } | |
| 40 | 41 | for _, tt := range tests { | |
| 41 | 42 | t.Run(tt.path, func(t *testing.T) { | |
| 43 | + | t.Parallel() | |
| 42 | 44 | r, rest, ok := resolveRef(refs, tt.path) | |
| 43 | 45 | assert.Equal(t, tt.ok, ok) | |
| 44 | 46 | if !ok { | |
| @@ -52,6 +54,7 @@ func TestResolveRef(t *testing.T) { | |||
| 52 | 54 | } | |
| 53 | 55 | ||
| 54 | 56 | func TestRepoHref(t *testing.T) { | |
| 57 | + | t.Parallel() | |
| 55 | 58 | assert.Equal(t, "/demo", repoHref("demo")) | |
| 56 | 59 | assert.Equal(t, "/demo/tree/feature/x/a%20b/c%23.go", repoHref("demo", "tree", "feature/x", "a b/c#.go")) | |
| 57 | 60 | assert.Equal(t, "/demo/blob/main/%3Fq", repoHref("demo", "blob", "main", "", "?q")) | |
| @@ -16,6 +16,7 @@ func ref(kind pb.RefKind, short string) *pb.Ref { | |||||
| 16 | 16 | ||||
| 17 | // AC3: a ref with slashes is found by the longest matching prefix. | 17 | // AC3: a ref with slashes is found by the longest matching prefix. | ||
| 18 | func TestResolveRef(t *testing.T) { | 18 | func TestResolveRef(t *testing.T) { | ||
| 19 | + | t.Parallel() | |||
| 19 | b, tag := pb.RefKind_REF_KIND_BRANCH, pb.RefKind_REF_KIND_TAG | 20 | b, tag := pb.RefKind_REF_KIND_BRANCH, pb.RefKind_REF_KIND_TAG | ||
| 20 | refs := []*pb.Ref{ | 21 | refs := []*pb.Ref{ | ||
| 21 | ref(b, "main"), ref(b, "feature"), ref(b, "feature/x"), ref(b, "feature/x/y"), ref(tag, "v1/rc"), | 22 | ref(b, "main"), ref(b, "feature"), ref(b, "feature/x"), ref(b, "feature/x/y"), ref(tag, "v1/rc"), | ||
| @@ -39,6 +40,7 @@ func TestResolveRef(t *testing.T) { | |||||
| 39 | } | 40 | } | ||
| 40 | for _, tt := range tests { | 41 | for _, tt := range tests { | ||
| 41 | t.Run(tt.path, func(t *testing.T) { | 42 | t.Run(tt.path, func(t *testing.T) { | ||
| 43 | + | t.Parallel() | |||
| 42 | r, rest, ok := resolveRef(refs, tt.path) | 44 | r, rest, ok := resolveRef(refs, tt.path) | ||
| 43 | assert.Equal(t, tt.ok, ok) | 45 | assert.Equal(t, tt.ok, ok) | ||
| 44 | if !ok { | 46 | if !ok { | ||
| @@ -52,6 +54,7 @@ func TestResolveRef(t *testing.T) { | |||||
| 52 | } | 54 | } | ||
| 53 | 55 | ||||
| 54 | func TestRepoHref(t *testing.T) { | 56 | func TestRepoHref(t *testing.T) { | ||
| 57 | + | t.Parallel() | |||
| 55 | assert.Equal(t, "/demo", repoHref("demo")) | 58 | assert.Equal(t, "/demo", repoHref("demo")) | ||
| 56 | assert.Equal(t, "/demo/tree/feature/x/a%20b/c%23.go", repoHref("demo", "tree", "feature/x", "a b/c#.go")) | 59 | assert.Equal(t, "/demo/tree/feature/x/a%20b/c%23.go", repoHref("demo", "tree", "feature/x", "a b/c#.go")) | ||
| 57 | assert.Equal(t, "/demo/blob/main/%3Fq", repoHref("demo", "blob", "main", "", "?q")) | 60 | assert.Equal(t, "/demo/blob/main/%3Fq", repoHref("demo", "blob", "main", "", "?q")) | ||
M internal/adapters/web/rules_test.go +1 -0
| @@ -13,6 +13,7 @@ import ( | |||
| 13 | 13 | // The speculation rules prefetch the light pages and leave the heavy ones | |
| 14 | 14 | // (several Git reads each) to a click. | |
| 15 | 15 | func TestSpeculationRulesSkipHeavyPages(t *testing.T) { | |
| 16 | + | t.Parallel() | |
| 16 | 17 | raw, err := staticFS.ReadFile("static/rules.json") | |
| 17 | 18 | require.NoError(t, err) | |
| 18 | 19 | var rules struct { | |
| @@ -13,6 +13,7 @@ import ( | |||||
| 13 | // The speculation rules prefetch the light pages and leave the heavy ones | 13 | // The speculation rules prefetch the light pages and leave the heavy ones | ||
| 14 | // (several Git reads each) to a click. | 14 | // (several Git reads each) to a click. | ||
| 15 | func TestSpeculationRulesSkipHeavyPages(t *testing.T) { | 15 | func TestSpeculationRulesSkipHeavyPages(t *testing.T) { | ||
| 16 | + | t.Parallel() | |||
| 16 | raw, err := staticFS.ReadFile("static/rules.json") | 17 | raw, err := staticFS.ReadFile("static/rules.json") | ||
| 17 | require.NoError(t, err) | 18 | require.NoError(t, err) | ||
| 18 | var rules struct { | 19 | var rules struct { | ||
M internal/adapters/web/session_test.go +8 -0
| @@ -57,6 +57,7 @@ func newSessionServer(t *testing.T, sess SessionAuth, publicURL string) (*Server | |||
| 57 | 57 | } | |
| 58 | 58 | ||
| 59 | 59 | func TestOriginOf(t *testing.T) { | |
| 60 | + | t.Parallel() | |
| 60 | 61 | for in, want := range map[string]string{ | |
| 61 | 62 | "https://forge.example": "https://forge.example", | |
| 62 | 63 | "https://Forge.Example/": "https://forge.example", | |
| @@ -75,6 +76,7 @@ func TestOriginOf(t *testing.T) { | |||
| 75 | 76 | } | |
| 76 | 77 | ||
| 77 | 78 | func TestValidNonce(t *testing.T) { | |
| 79 | + | t.Parallel() | |
| 78 | 80 | assert.True(t, validNonce(strings.Repeat("aZ09-_", 7)+"a")) | |
| 79 | 81 | for _, n := range []string{"", "short", strings.Repeat("a", 44), strings.Repeat("a", 42) + "=", strings.Repeat("a", 42) + "/", strings.Repeat("é", 22)} { | |
| 80 | 82 | assert.False(t, validNonce(n), n) | |
| @@ -84,6 +86,7 @@ func TestValidNonce(t *testing.T) { | |||
| 84 | 86 | // The gate: a POST needs the Origin of the public URL, whatever Host says, and | |
| 85 | 87 | // a session unless it is the login. | |
| 86 | 88 | func TestGate(t *testing.T) { | |
| 89 | + | t.Parallel() | |
| 87 | 90 | srv, lim := newSessionServer(t, &fakeSessions{valid: "tok"}, "https://forge.example") | |
| 88 | 91 | do := func(path, origin, cookie string) int { | |
| 89 | 92 | req := httptest.NewRequest(http.MethodPost, path, nil) | |
| @@ -122,6 +125,7 @@ func TestGate(t *testing.T) { | |||
| 122 | 125 | // one the router matches, and on exactly loginPrefix + nonce. An encoded slash | |
| 123 | 126 | // must not turn another route into a "login". | |
| 124 | 127 | func TestGateLoginExemptionIsExact(t *testing.T) { | |
| 128 | + | t.Parallel() | |
| 125 | 129 | sess := &fakeSessions{valid: "tok"} | |
| 126 | 130 | lim := &countingLimiter{} | |
| 127 | 131 | srv, err := New(Config{ | |
| @@ -168,6 +172,7 @@ func TestGateLoginExemptionIsExact(t *testing.T) { | |||
| 168 | 172 | // A session check that cannot be made fails closed (anonymous) and keeps the | |
| 169 | 173 | // cookie; a check that says no clears it. | |
| 170 | 174 | func TestWithSessionFailsClosed(t *testing.T) { | |
| 175 | + | t.Parallel() | |
| 171 | 176 | broken := &fakeSessions{valid: "tok", err: errors.New("kv down")} | |
| 172 | 177 | srv, _ := newSessionServer(t, broken, "https://forge.example") | |
| 173 | 178 | req := httptest.NewRequest(http.MethodGet, "/", nil) | |
| @@ -194,6 +199,7 @@ func TestWithSessionFailsClosed(t *testing.T) { | |||
| 194 | 199 | // varies on Cookie, and nothing later in the request undoes it: the page | |
| 195 | 200 | // writer, the raw file handler and the event stream keep it. | |
| 196 | 201 | func TestInvalidCookieAnswerIsNotCached(t *testing.T) { | |
| 202 | + | t.Parallel() | |
| 197 | 203 | sess := &fakeSessions{valid: "tok"} | |
| 198 | 204 | srv, _ := newSessionServer(t, sess, "https://forge.example") | |
| 199 | 205 | ||
| @@ -251,6 +257,7 @@ func TestInvalidCookieAnswerIsNotCached(t *testing.T) { | |||
| 251 | 257 | ||
| 252 | 258 | // Without sessions configured there is no login route and no cookie is read. | |
| 253 | 259 | func TestNoSessionsNoRoutes(t *testing.T) { | |
| 260 | + | t.Parallel() | |
| 254 | 261 | srv, err := New(Config{ | |
| 255 | 262 | Repos: failing{err: errors.New("x")}, Git: ermiteconnect.NewGitServiceClient(http.DefaultClient, "http://unused.invalid"), | |
| 256 | 263 | PublicURL: "https://forge.example", SourceURL: "https://forge.example/ermite", Version: "v1", | |
| @@ -268,6 +275,7 @@ func TestNoSessionsNoRoutes(t *testing.T) { | |||
| 268 | 275 | } | |
| 269 | 276 | ||
| 270 | 277 | func TestLogPathHidesLinks(t *testing.T) { | |
| 278 | + | t.Parallel() | |
| 271 | 279 | assert.Equal(t, "/-/login/…", logPath("/-/login/"+strings.Repeat("a", 43))) | |
| 272 | 280 | assert.Equal(t, "/demo/tree", logPath("/demo/tree")) | |
| 273 | 281 | for _, p := range []string{"//-/login/secret", "/x/../-/login/secret", "/-/login/secret/more", "/-/./login/secret"} { | |
| @@ -57,6 +57,7 @@ func newSessionServer(t *testing.T, sess SessionAuth, publicURL string) (*Server | |||||
| 57 | } | 57 | } | ||
| 58 | 58 | ||||
| 59 | func TestOriginOf(t *testing.T) { | 59 | func TestOriginOf(t *testing.T) { | ||
| 60 | + | t.Parallel() | |||
| 60 | for in, want := range map[string]string{ | 61 | for in, want := range map[string]string{ | ||
| 61 | "https://forge.example": "https://forge.example", | 62 | "https://forge.example": "https://forge.example", | ||
| 62 | "https://Forge.Example/": "https://forge.example", | 63 | "https://Forge.Example/": "https://forge.example", | ||
| @@ -75,6 +76,7 @@ func TestOriginOf(t *testing.T) { | |||||
| 75 | } | 76 | } | ||
| 76 | 77 | ||||
| 77 | func TestValidNonce(t *testing.T) { | 78 | func TestValidNonce(t *testing.T) { | ||
| 79 | + | t.Parallel() | |||
| 78 | assert.True(t, validNonce(strings.Repeat("aZ09-_", 7)+"a")) | 80 | assert.True(t, validNonce(strings.Repeat("aZ09-_", 7)+"a")) | ||
| 79 | for _, n := range []string{"", "short", strings.Repeat("a", 44), strings.Repeat("a", 42) + "=", strings.Repeat("a", 42) + "/", strings.Repeat("é", 22)} { | 81 | for _, n := range []string{"", "short", strings.Repeat("a", 44), strings.Repeat("a", 42) + "=", strings.Repeat("a", 42) + "/", strings.Repeat("é", 22)} { | ||
| 80 | assert.False(t, validNonce(n), n) | 82 | assert.False(t, validNonce(n), n) | ||
| @@ -84,6 +86,7 @@ func TestValidNonce(t *testing.T) { | |||||
| 84 | // The gate: a POST needs the Origin of the public URL, whatever Host says, and | 86 | // The gate: a POST needs the Origin of the public URL, whatever Host says, and | ||
| 85 | // a session unless it is the login. | 87 | // a session unless it is the login. | ||
| 86 | func TestGate(t *testing.T) { | 88 | func TestGate(t *testing.T) { | ||
| 89 | + | t.Parallel() | |||
| 87 | srv, lim := newSessionServer(t, &fakeSessions{valid: "tok"}, "https://forge.example") | 90 | srv, lim := newSessionServer(t, &fakeSessions{valid: "tok"}, "https://forge.example") | ||
| 88 | do := func(path, origin, cookie string) int { | 91 | do := func(path, origin, cookie string) int { | ||
| 89 | req := httptest.NewRequest(http.MethodPost, path, nil) | 92 | req := httptest.NewRequest(http.MethodPost, path, nil) | ||
| @@ -122,6 +125,7 @@ func TestGate(t *testing.T) { | |||||
| 122 | // one the router matches, and on exactly loginPrefix + nonce. An encoded slash | 125 | // one the router matches, and on exactly loginPrefix + nonce. An encoded slash | ||
| 123 | // must not turn another route into a "login". | 126 | // must not turn another route into a "login". | ||
| 124 | func TestGateLoginExemptionIsExact(t *testing.T) { | 127 | func TestGateLoginExemptionIsExact(t *testing.T) { | ||
| 128 | + | t.Parallel() | |||
| 125 | sess := &fakeSessions{valid: "tok"} | 129 | sess := &fakeSessions{valid: "tok"} | ||
| 126 | lim := &countingLimiter{} | 130 | lim := &countingLimiter{} | ||
| 127 | srv, err := New(Config{ | 131 | srv, err := New(Config{ | ||
| @@ -168,6 +172,7 @@ func TestGateLoginExemptionIsExact(t *testing.T) { | |||||
| 168 | // A session check that cannot be made fails closed (anonymous) and keeps the | 172 | // A session check that cannot be made fails closed (anonymous) and keeps the | ||
| 169 | // cookie; a check that says no clears it. | 173 | // cookie; a check that says no clears it. | ||
| 170 | func TestWithSessionFailsClosed(t *testing.T) { | 174 | func TestWithSessionFailsClosed(t *testing.T) { | ||
| 175 | + | t.Parallel() | |||
| 171 | broken := &fakeSessions{valid: "tok", err: errors.New("kv down")} | 176 | broken := &fakeSessions{valid: "tok", err: errors.New("kv down")} | ||
| 172 | srv, _ := newSessionServer(t, broken, "https://forge.example") | 177 | srv, _ := newSessionServer(t, broken, "https://forge.example") | ||
| 173 | req := httptest.NewRequest(http.MethodGet, "/", nil) | 178 | req := httptest.NewRequest(http.MethodGet, "/", nil) | ||
| @@ -194,6 +199,7 @@ func TestWithSessionFailsClosed(t *testing.T) { | |||||
| 194 | // varies on Cookie, and nothing later in the request undoes it: the page | 199 | // varies on Cookie, and nothing later in the request undoes it: the page | ||
| 195 | // writer, the raw file handler and the event stream keep it. | 200 | // writer, the raw file handler and the event stream keep it. | ||
| 196 | func TestInvalidCookieAnswerIsNotCached(t *testing.T) { | 201 | func TestInvalidCookieAnswerIsNotCached(t *testing.T) { | ||
| 202 | + | t.Parallel() | |||
| 197 | sess := &fakeSessions{valid: "tok"} | 203 | sess := &fakeSessions{valid: "tok"} | ||
| 198 | srv, _ := newSessionServer(t, sess, "https://forge.example") | 204 | srv, _ := newSessionServer(t, sess, "https://forge.example") | ||
| 199 | 205 | ||||
| @@ -251,6 +257,7 @@ func TestInvalidCookieAnswerIsNotCached(t *testing.T) { | |||||
| 251 | 257 | ||||
| 252 | // Without sessions configured there is no login route and no cookie is read. | 258 | // Without sessions configured there is no login route and no cookie is read. | ||
| 253 | func TestNoSessionsNoRoutes(t *testing.T) { | 259 | func TestNoSessionsNoRoutes(t *testing.T) { | ||
| 260 | + | t.Parallel() | |||
| 254 | srv, err := New(Config{ | 261 | srv, err := New(Config{ | ||
| 255 | Repos: failing{err: errors.New("x")}, Git: ermiteconnect.NewGitServiceClient(http.DefaultClient, "http://unused.invalid"), | 262 | Repos: failing{err: errors.New("x")}, Git: ermiteconnect.NewGitServiceClient(http.DefaultClient, "http://unused.invalid"), | ||
| 256 | PublicURL: "https://forge.example", SourceURL: "https://forge.example/ermite", Version: "v1", | 263 | PublicURL: "https://forge.example", SourceURL: "https://forge.example/ermite", Version: "v1", | ||
| @@ -268,6 +275,7 @@ func TestNoSessionsNoRoutes(t *testing.T) { | |||||
| 268 | } | 275 | } | ||
| 269 | 276 | ||||
| 270 | func TestLogPathHidesLinks(t *testing.T) { | 277 | func TestLogPathHidesLinks(t *testing.T) { | ||
| 278 | + | t.Parallel() | |||
| 271 | assert.Equal(t, "/-/login/…", logPath("/-/login/"+strings.Repeat("a", 43))) | 279 | assert.Equal(t, "/-/login/…", logPath("/-/login/"+strings.Repeat("a", 43))) | ||
| 272 | assert.Equal(t, "/demo/tree", logPath("/demo/tree")) | 280 | assert.Equal(t, "/demo/tree", logPath("/demo/tree")) | ||
| 273 | for _, p := range []string{"//-/login/secret", "/x/../-/login/secret", "/-/login/secret/more", "/-/./login/secret"} { | 281 | for _, p := range []string{"//-/login/secret", "/x/../-/login/secret", "/-/login/secret/more", "/-/./login/secret"} { | ||
M internal/adapters/web/text_test.go +1 -0
| @@ -10,6 +10,7 @@ import ( | |||
| 10 | 10 | ||
| 11 | 11 | // Invisible characters in names and subjects show as markers. | |
| 12 | 12 | func TestVisibleText(t *testing.T) { | |
| 13 | + | t.Parallel() | |
| 13 | 14 | assert.Equal(t, "a<U+202E>b.go", visibleText("a\u202eb.go")) | |
| 14 | 15 | assert.Equal(t, "plain π\t“é”", visibleText("plain π\t“é”")) | |
| 15 | 16 | assert.Equal(t, "a<U+0000>b<U+200B>", visibleText("a\x00b\u200b")) | |
| @@ -10,6 +10,7 @@ import ( | |||||
| 10 | 10 | ||||
| 11 | // Invisible characters in names and subjects show as markers. | 11 | // Invisible characters in names and subjects show as markers. | ||
| 12 | func TestVisibleText(t *testing.T) { | 12 | func TestVisibleText(t *testing.T) { | ||
| 13 | + | t.Parallel() | |||
| 13 | assert.Equal(t, "a<U+202E>b.go", visibleText("a\u202eb.go")) | 14 | assert.Equal(t, "a<U+202E>b.go", visibleText("a\u202eb.go")) | ||
| 14 | assert.Equal(t, "plain π\t“é”", visibleText("plain π\t“é”")) | 15 | assert.Equal(t, "plain π\t“é”", visibleText("plain π\t“é”")) | ||
| 15 | assert.Equal(t, "a<U+0000>b<U+200B>", visibleText("a\x00b\u200b")) | 16 | assert.Equal(t, "a<U+0000>b<U+200B>", visibleText("a\x00b\u200b")) | ||
M internal/app/browse_test.go +12 -0
| @@ -106,6 +106,7 @@ func browseOps() []browseOp { | |||
| 106 | 106 | // not found; stored tokens need repo:read on the repository; and the Git | |
| 107 | 107 | // reader is never reached when access is refused. | |
| 108 | 108 | func TestBrowseAccess(t *testing.T) { | |
| 109 | + | t.Parallel() | |
| 109 | 110 | ctx := context.Background() | |
| 110 | 111 | pub := domain.Repo{Name: name(t, "pub"), Visibility: domain.VisibilityPublic} | |
| 111 | 112 | priv := domain.Repo{Name: name(t, "priv"), Visibility: domain.VisibilityPrivate} | |
| @@ -129,6 +130,7 @@ func TestBrowseAccess(t *testing.T) { | |||
| 129 | 130 | for _, pr := range principals { | |
| 130 | 131 | for _, repo := range []domain.Repo{pub, priv} { | |
| 131 | 132 | t.Run(op.name+"/"+pr.name+"/"+repo.Name.String(), func(t *testing.T) { | |
| 133 | + | t.Parallel() | |
| 132 | 134 | f := newBrowseFixture(t) | |
| 133 | 135 | f.meta.EXPECT().Get(ctx, repo.Name).Return(repo, nil) | |
| 134 | 136 | allowed := pr.pub | |
| @@ -149,12 +151,14 @@ func TestBrowseAccess(t *testing.T) { | |||
| 149 | 151 | } | |
| 150 | 152 | } | |
| 151 | 153 | t.Run(op.name+"/missing repository", func(t *testing.T) { | |
| 154 | + | t.Parallel() | |
| 152 | 155 | f := newBrowseFixture(t) | |
| 153 | 156 | f.meta.EXPECT().Get(ctx, name(t, "nope")).Return(domain.Repo{}, domain.ErrNotFound) | |
| 154 | 157 | err := op.call(f.browse, ctx, domain.Root(), name(t, "nope")) | |
| 155 | 158 | assert.ErrorIs(t, err, domain.ErrNotFound) | |
| 156 | 159 | }) | |
| 157 | 160 | t.Run(op.name+"/zero name", func(t *testing.T) { | |
| 161 | + | t.Parallel() | |
| 158 | 162 | f := newBrowseFixture(t) | |
| 159 | 163 | err := op.call(f.browse, ctx, domain.Root(), domain.RepoName{}) | |
| 160 | 164 | assert.ErrorIs(t, err, domain.ErrInvalidName) | |
| @@ -163,6 +167,7 @@ func TestBrowseAccess(t *testing.T) { | |||
| 163 | 167 | } | |
| 164 | 168 | ||
| 165 | 169 | func TestBrowseHidesPrivateOnReaderErrors(t *testing.T) { | |
| 170 | + | t.Parallel() | |
| 166 | 171 | // A reader error is passed through for a caller who may read. | |
| 167 | 172 | ctx := context.Background() | |
| 168 | 173 | f := newBrowseFixture(t) | |
| @@ -178,6 +183,7 @@ func ref(kind domain.RefKind, name string) domain.Ref { | |||
| 178 | 183 | } | |
| 179 | 184 | ||
| 180 | 185 | func TestListRefsPagingAndKinds(t *testing.T) { | |
| 186 | + | t.Parallel() | |
| 181 | 187 | ctx := context.Background() | |
| 182 | 188 | pub := domain.Repo{Name: name(t, "pub"), Visibility: domain.VisibilityPublic} | |
| 183 | 189 | all := []domain.Ref{ | |
| @@ -258,6 +264,7 @@ func TestListRefsPagingAndKinds(t *testing.T) { | |||
| 258 | 264 | }) | |
| 259 | 265 | ||
| 260 | 266 | t.Run("bad input never reaches the reader", func(t *testing.T) { | |
| 267 | + | t.Parallel() | |
| 261 | 268 | f := newBrowseFixture(t) | |
| 262 | 269 | for _, params := range []app.ListRefsParams{ | |
| 263 | 270 | {PageSize: -1}, | |
| @@ -278,11 +285,13 @@ func TestListRefsPagingAndKinds(t *testing.T) { | |||
| 278 | 285 | } | |
| 279 | 286 | ||
| 280 | 287 | func TestListCommitsPaging(t *testing.T) { | |
| 288 | + | t.Parallel() | |
| 281 | 289 | ctx := context.Background() | |
| 282 | 290 | pub := domain.Repo{Name: name(t, "pub"), Visibility: domain.VisibilityPublic} | |
| 283 | 291 | p := mustPath(t, "d/f") | |
| 284 | 292 | ||
| 285 | 293 | t.Run("first page asks for one more than the page and returns a token", func(t *testing.T) { | |
| 294 | + | t.Parallel() | |
| 286 | 295 | f := newBrowseFixture(t) | |
| 287 | 296 | f.meta.EXPECT().Get(ctx, pub.Name).Return(pub, nil) | |
| 288 | 297 | f.reader.EXPECT().ListCommits(ctx, pub.Name, ports.CommitQuery{Path: p, Limit: 10}). | |
| @@ -308,6 +317,7 @@ func TestListCommitsPaging(t *testing.T) { | |||
| 308 | 317 | }) | |
| 309 | 318 | ||
| 310 | 319 | t.Run("a token is bound to its path and must be well formed", func(t *testing.T) { | |
| 320 | + | t.Parallel() | |
| 311 | 321 | f := newBrowseFixture(t) | |
| 312 | 322 | f.meta.EXPECT().Get(ctx, pub.Name).Return(pub, nil) | |
| 313 | 323 | f.reader.EXPECT().ListCommits(ctx, pub.Name, mock.Anything). | |
| @@ -328,6 +338,7 @@ func TestListCommitsPaging(t *testing.T) { | |||
| 328 | 338 | }) | |
| 329 | 339 | ||
| 330 | 340 | t.Run("a token cannot skip deeper than the cap", func(t *testing.T) { | |
| 341 | + | t.Parallel() | |
| 331 | 342 | f := newBrowseFixture(t) | |
| 332 | 343 | f.meta.EXPECT().Get(ctx, pub.Name).Return(pub, nil).Maybe() | |
| 333 | 344 | f.reader.EXPECT().ListCommits(ctx, pub.Name, mock.Anything). | |
| @@ -352,6 +363,7 @@ func TestListCommitsPaging(t *testing.T) { | |||
| 352 | 363 | }) | |
| 353 | 364 | ||
| 354 | 365 | t.Run("page size", func(t *testing.T) { | |
| 366 | + | t.Parallel() | |
| 355 | 367 | f := newBrowseFixture(t) | |
| 356 | 368 | f.meta.EXPECT().Get(ctx, pub.Name).Return(pub, nil).Times(2) | |
| 357 | 369 | f.reader.EXPECT().ListCommits(ctx, pub.Name, ports.CommitQuery{Limit: app.DefaultPageSize}).Return(ports.CommitPage{}, nil) | |
| @@ -106,6 +106,7 @@ func browseOps() []browseOp { | |||||
| 106 | // not found; stored tokens need repo:read on the repository; and the Git | 106 | // not found; stored tokens need repo:read on the repository; and the Git | ||
| 107 | // reader is never reached when access is refused. | 107 | // reader is never reached when access is refused. | ||
| 108 | func TestBrowseAccess(t *testing.T) { | 108 | func TestBrowseAccess(t *testing.T) { | ||
| 109 | + | t.Parallel() | |||
| 109 | ctx := context.Background() | 110 | ctx := context.Background() | ||
| 110 | pub := domain.Repo{Name: name(t, "pub"), Visibility: domain.VisibilityPublic} | 111 | pub := domain.Repo{Name: name(t, "pub"), Visibility: domain.VisibilityPublic} | ||
| 111 | priv := domain.Repo{Name: name(t, "priv"), Visibility: domain.VisibilityPrivate} | 112 | priv := domain.Repo{Name: name(t, "priv"), Visibility: domain.VisibilityPrivate} | ||
| @@ -129,6 +130,7 @@ func TestBrowseAccess(t *testing.T) { | |||||
| 129 | for _, pr := range principals { | 130 | for _, pr := range principals { | ||
| 130 | for _, repo := range []domain.Repo{pub, priv} { | 131 | for _, repo := range []domain.Repo{pub, priv} { | ||
| 131 | t.Run(op.name+"/"+pr.name+"/"+repo.Name.String(), func(t *testing.T) { | 132 | t.Run(op.name+"/"+pr.name+"/"+repo.Name.String(), func(t *testing.T) { | ||
| 133 | + | t.Parallel() | |||
| 132 | f := newBrowseFixture(t) | 134 | f := newBrowseFixture(t) | ||
| 133 | f.meta.EXPECT().Get(ctx, repo.Name).Return(repo, nil) | 135 | f.meta.EXPECT().Get(ctx, repo.Name).Return(repo, nil) | ||
| 134 | allowed := pr.pub | 136 | allowed := pr.pub | ||
| @@ -149,12 +151,14 @@ func TestBrowseAccess(t *testing.T) { | |||||
| 149 | } | 151 | } | ||
| 150 | } | 152 | } | ||
| 151 | t.Run(op.name+"/missing repository", func(t *testing.T) { | 153 | t.Run(op.name+"/missing repository", func(t *testing.T) { | ||
| 154 | + | t.Parallel() | |||
| 152 | f := newBrowseFixture(t) | 155 | f := newBrowseFixture(t) | ||
| 153 | f.meta.EXPECT().Get(ctx, name(t, "nope")).Return(domain.Repo{}, domain.ErrNotFound) | 156 | f.meta.EXPECT().Get(ctx, name(t, "nope")).Return(domain.Repo{}, domain.ErrNotFound) | ||
| 154 | err := op.call(f.browse, ctx, domain.Root(), name(t, "nope")) | 157 | err := op.call(f.browse, ctx, domain.Root(), name(t, "nope")) | ||
| 155 | assert.ErrorIs(t, err, domain.ErrNotFound) | 158 | assert.ErrorIs(t, err, domain.ErrNotFound) | ||
| 156 | }) | 159 | }) | ||
| 157 | t.Run(op.name+"/zero name", func(t *testing.T) { | 160 | t.Run(op.name+"/zero name", func(t *testing.T) { | ||
| 161 | + | t.Parallel() | |||
| 158 | f := newBrowseFixture(t) | 162 | f := newBrowseFixture(t) | ||
| 159 | err := op.call(f.browse, ctx, domain.Root(), domain.RepoName{}) | 163 | err := op.call(f.browse, ctx, domain.Root(), domain.RepoName{}) | ||
| 160 | assert.ErrorIs(t, err, domain.ErrInvalidName) | 164 | assert.ErrorIs(t, err, domain.ErrInvalidName) | ||
| @@ -163,6 +167,7 @@ func TestBrowseAccess(t *testing.T) { | |||||
| 163 | } | 167 | } | ||
| 164 | 168 | ||||
| 165 | func TestBrowseHidesPrivateOnReaderErrors(t *testing.T) { | 169 | func TestBrowseHidesPrivateOnReaderErrors(t *testing.T) { | ||
| 170 | + | t.Parallel() | |||
| 166 | // A reader error is passed through for a caller who may read. | 171 | // A reader error is passed through for a caller who may read. | ||
| 167 | ctx := context.Background() | 172 | ctx := context.Background() | ||
| 168 | f := newBrowseFixture(t) | 173 | f := newBrowseFixture(t) | ||
| @@ -178,6 +183,7 @@ func ref(kind domain.RefKind, name string) domain.Ref { | |||||
| 178 | } | 183 | } | ||
| 179 | 184 | ||||
| 180 | func TestListRefsPagingAndKinds(t *testing.T) { | 185 | func TestListRefsPagingAndKinds(t *testing.T) { | ||
| 186 | + | t.Parallel() | |||
| 181 | ctx := context.Background() | 187 | ctx := context.Background() | ||
| 182 | pub := domain.Repo{Name: name(t, "pub"), Visibility: domain.VisibilityPublic} | 188 | pub := domain.Repo{Name: name(t, "pub"), Visibility: domain.VisibilityPublic} | ||
| 183 | all := []domain.Ref{ | 189 | all := []domain.Ref{ | ||
| @@ -258,6 +264,7 @@ func TestListRefsPagingAndKinds(t *testing.T) { | |||||
| 258 | }) | 264 | }) | ||
| 259 | 265 | ||||
| 260 | t.Run("bad input never reaches the reader", func(t *testing.T) { | 266 | t.Run("bad input never reaches the reader", func(t *testing.T) { | ||
| 267 | + | t.Parallel() | |||
| 261 | f := newBrowseFixture(t) | 268 | f := newBrowseFixture(t) | ||
| 262 | for _, params := range []app.ListRefsParams{ | 269 | for _, params := range []app.ListRefsParams{ | ||
| 263 | {PageSize: -1}, | 270 | {PageSize: -1}, | ||
| @@ -278,11 +285,13 @@ func TestListRefsPagingAndKinds(t *testing.T) { | |||||
| 278 | } | 285 | } | ||
| 279 | 286 | ||||
| 280 | func TestListCommitsPaging(t *testing.T) { | 287 | func TestListCommitsPaging(t *testing.T) { | ||
| 288 | + | t.Parallel() | |||
| 281 | ctx := context.Background() | 289 | ctx := context.Background() | ||
| 282 | pub := domain.Repo{Name: name(t, "pub"), Visibility: domain.VisibilityPublic} | 290 | pub := domain.Repo{Name: name(t, "pub"), Visibility: domain.VisibilityPublic} | ||
| 283 | p := mustPath(t, "d/f") | 291 | p := mustPath(t, "d/f") | ||
| 284 | 292 | ||||
| 285 | t.Run("first page asks for one more than the page and returns a token", func(t *testing.T) { | 293 | t.Run("first page asks for one more than the page and returns a token", func(t *testing.T) { | ||
| 294 | + | t.Parallel() | |||
| 286 | f := newBrowseFixture(t) | 295 | f := newBrowseFixture(t) | ||
| 287 | f.meta.EXPECT().Get(ctx, pub.Name).Return(pub, nil) | 296 | f.meta.EXPECT().Get(ctx, pub.Name).Return(pub, nil) | ||
| 288 | f.reader.EXPECT().ListCommits(ctx, pub.Name, ports.CommitQuery{Path: p, Limit: 10}). | 297 | f.reader.EXPECT().ListCommits(ctx, pub.Name, ports.CommitQuery{Path: p, Limit: 10}). | ||
| @@ -308,6 +317,7 @@ func TestListCommitsPaging(t *testing.T) { | |||||
| 308 | }) | 317 | }) | ||
| 309 | 318 | ||||
| 310 | t.Run("a token is bound to its path and must be well formed", func(t *testing.T) { | 319 | t.Run("a token is bound to its path and must be well formed", func(t *testing.T) { | ||
| 320 | + | t.Parallel() | |||
| 311 | f := newBrowseFixture(t) | 321 | f := newBrowseFixture(t) | ||
| 312 | f.meta.EXPECT().Get(ctx, pub.Name).Return(pub, nil) | 322 | f.meta.EXPECT().Get(ctx, pub.Name).Return(pub, nil) | ||
| 313 | f.reader.EXPECT().ListCommits(ctx, pub.Name, mock.Anything). | 323 | f.reader.EXPECT().ListCommits(ctx, pub.Name, mock.Anything). | ||
| @@ -328,6 +338,7 @@ func TestListCommitsPaging(t *testing.T) { | |||||
| 328 | }) | 338 | }) | ||
| 329 | 339 | ||||
| 330 | t.Run("a token cannot skip deeper than the cap", func(t *testing.T) { | 340 | t.Run("a token cannot skip deeper than the cap", func(t *testing.T) { | ||
| 341 | + | t.Parallel() | |||
| 331 | f := newBrowseFixture(t) | 342 | f := newBrowseFixture(t) | ||
| 332 | f.meta.EXPECT().Get(ctx, pub.Name).Return(pub, nil).Maybe() | 343 | f.meta.EXPECT().Get(ctx, pub.Name).Return(pub, nil).Maybe() | ||
| 333 | f.reader.EXPECT().ListCommits(ctx, pub.Name, mock.Anything). | 344 | f.reader.EXPECT().ListCommits(ctx, pub.Name, mock.Anything). | ||
| @@ -352,6 +363,7 @@ func TestListCommitsPaging(t *testing.T) { | |||||
| 352 | }) | 363 | }) | ||
| 353 | 364 | ||||
| 354 | t.Run("page size", func(t *testing.T) { | 365 | t.Run("page size", func(t *testing.T) { | ||
| 366 | + | t.Parallel() | |||
| 355 | f := newBrowseFixture(t) | 367 | f := newBrowseFixture(t) | ||
| 356 | f.meta.EXPECT().Get(ctx, pub.Name).Return(pub, nil).Times(2) | 368 | f.meta.EXPECT().Get(ctx, pub.Name).Return(pub, nil).Times(2) | ||
| 357 | f.reader.EXPECT().ListCommits(ctx, pub.Name, ports.CommitQuery{Limit: app.DefaultPageSize}).Return(ports.CommitPage{}, nil) | 369 | f.reader.EXPECT().ListCommits(ctx, pub.Name, ports.CommitQuery{Limit: app.DefaultPageSize}).Return(ports.CommitPage{}, nil) | ||
M internal/app/browsecache_test.go +11 -0
| @@ -35,6 +35,7 @@ func treeWithLast(c string) domain.Tree { | |||
| 35 | 35 | // TestGetTreeLastCommitCache covers which GetTree calls are served from the | |
| 36 | 36 | // cache: only the ones with last commits at a full commit id. | |
| 37 | 37 | func TestGetTreeLastCommitCache(t *testing.T) { | |
| 38 | + | t.Parallel() | |
| 38 | 39 | ctx := context.Background() | |
| 39 | 40 | pub := domain.Repo{Name: name(t, "pub"), Visibility: domain.VisibilityPublic} | |
| 40 | 41 | full := domain.RevisionOf(oid("a")) | |
| @@ -55,6 +56,7 @@ func TestGetTreeLastCommitCache(t *testing.T) { | |||
| 55 | 56 | } | |
| 56 | 57 | for _, tc := range tests { | |
| 57 | 58 | t.Run(tc.name, func(t *testing.T) { | |
| 59 | + | t.Parallel() | |
| 58 | 60 | f := newBrowseFixture(t) | |
| 59 | 61 | f.meta.EXPECT().Get(mock.Anything, pub.Name).Return(pub, nil) | |
| 60 | 62 | f.reader.EXPECT().GetTree(mock.Anything, pub.Name, tc.rev, dir, tc.includeLast). | |
| @@ -69,6 +71,7 @@ func TestGetTreeLastCommitCache(t *testing.T) { | |||
| 69 | 71 | } | |
| 70 | 72 | ||
| 71 | 73 | func TestGetTreeLastCommitCacheKey(t *testing.T) { | |
| 74 | + | t.Parallel() | |
| 72 | 75 | ctx := context.Background() | |
| 73 | 76 | pubA := domain.Repo{Name: name(t, "a"), Visibility: domain.VisibilityPublic} | |
| 74 | 77 | pubB := domain.Repo{Name: name(t, "b"), Visibility: domain.VisibilityPublic} | |
| @@ -102,6 +105,7 @@ func TestGetTreeLastCommitCacheKey(t *testing.T) { | |||
| 102 | 105 | // TestGetTreeCacheKeepsAccessRule: a hit never skips the access check, and an | |
| 103 | 106 | // error is not cached. | |
| 104 | 107 | func TestGetTreeCacheKeepsAccessRule(t *testing.T) { | |
| 108 | + | t.Parallel() | |
| 105 | 109 | ctx := context.Background() | |
| 106 | 110 | priv := domain.Repo{Name: name(t, "priv"), Visibility: domain.VisibilityPrivate} | |
| 107 | 111 | rev, dir := domain.RevisionOf(oid("a")), treePath(t, "d") | |
| @@ -122,6 +126,7 @@ func TestGetTreeCacheKeepsAccessRule(t *testing.T) { | |||
| 122 | 126 | // TestGetTreeCacheReturnsCopies: a caller that edits its tree does not alter | |
| 123 | 127 | // what the next one gets. | |
| 124 | 128 | func TestGetTreeCacheReturnsCopies(t *testing.T) { | |
| 129 | + | t.Parallel() | |
| 125 | 130 | ctx := context.Background() | |
| 126 | 131 | pub := domain.Repo{Name: name(t, "pub"), Visibility: domain.VisibilityPublic} | |
| 127 | 132 | rev, dir := domain.RevisionOf(oid("a")), treePath(t, "d") | |
| @@ -143,6 +148,7 @@ func TestGetTreeCacheReturnsCopies(t *testing.T) { | |||
| 143 | 148 | // TestHistoryCacheBounds: the entry bound and the byte bound evict the least | |
| 144 | 149 | // recently used answer. | |
| 145 | 150 | func TestHistoryCacheBounds(t *testing.T) { | |
| 151 | + | t.Parallel() | |
| 146 | 152 | ctx := context.Background() | |
| 147 | 153 | pub := domain.Repo{Name: name(t, "pub"), Visibility: domain.VisibilityPublic} | |
| 148 | 154 | paths := []string{"p0", "p1", "p2"} | |
| @@ -167,6 +173,7 @@ func TestHistoryCacheBounds(t *testing.T) { | |||
| 167 | 173 | } | |
| 168 | 174 | for _, tc := range tests { | |
| 169 | 175 | t.Run(tc.name, func(t *testing.T) { | |
| 176 | + | t.Parallel() | |
| 170 | 177 | f := newBrowseFixture(t) | |
| 171 | 178 | f.browse.WithHistoryCache(tc.entries, tc.bytes) | |
| 172 | 179 | f.meta.EXPECT().Get(mock.Anything, pub.Name).Return(pub, nil) | |
| @@ -212,6 +219,7 @@ func TestHistoryCacheBounds(t *testing.T) { | |||
| 212 | 219 | // TestListCommitsLastChangeCache: the latest commit of a path at a full | |
| 213 | 220 | // commit id is cached; every other page shape is not. | |
| 214 | 221 | func TestListCommitsLastChangeCache(t *testing.T) { | |
| 222 | + | t.Parallel() | |
| 215 | 223 | ctx := context.Background() | |
| 216 | 224 | pub := domain.Repo{Name: name(t, "pub"), Visibility: domain.VisibilityPublic} | |
| 217 | 225 | full := domain.RevisionOf(oid("a")) | |
| @@ -234,6 +242,7 @@ func TestListCommitsLastChangeCache(t *testing.T) { | |||
| 234 | 242 | } | |
| 235 | 243 | for _, tc := range tests { | |
| 236 | 244 | t.Run(tc.name, func(t *testing.T) { | |
| 245 | + | t.Parallel() | |
| 237 | 246 | f := newBrowseFixture(t) | |
| 238 | 247 | f.meta.EXPECT().Get(mock.Anything, pub.Name).Return(pub, nil) | |
| 239 | 248 | q := ports.CommitQuery{Revision: tc.rev, Path: tc.path, Limit: tc.size} | |
| @@ -252,6 +261,7 @@ func TestListCommitsLastChangeCache(t *testing.T) { | |||
| 252 | 261 | } | |
| 253 | 262 | ||
| 254 | 263 | t.Run("a following page is not cached", func(t *testing.T) { | |
| 264 | + | t.Parallel() | |
| 255 | 265 | f := newBrowseFixture(t) | |
| 256 | 266 | f.meta.EXPECT().Get(mock.Anything, pub.Name).Return(pub, nil) | |
| 257 | 267 | f.reader.EXPECT().ListCommits(mock.Anything, pub.Name, ports.CommitQuery{Revision: full, Path: file, Limit: 1}). | |
| @@ -269,6 +279,7 @@ func TestListCommitsLastChangeCache(t *testing.T) { | |||
| 269 | 279 | } | |
| 270 | 280 | ||
| 271 | 281 | func TestHistoryCacheConcurrent(t *testing.T) { | |
| 282 | + | t.Parallel() | |
| 272 | 283 | ctx := context.Background() | |
| 273 | 284 | pub := domain.Repo{Name: name(t, "pub"), Visibility: domain.VisibilityPublic} | |
| 274 | 285 | f := newBrowseFixture(t) | |
| @@ -35,6 +35,7 @@ func treeWithLast(c string) domain.Tree { | |||||
| 35 | // TestGetTreeLastCommitCache covers which GetTree calls are served from the | 35 | // TestGetTreeLastCommitCache covers which GetTree calls are served from the | ||
| 36 | // cache: only the ones with last commits at a full commit id. | 36 | // cache: only the ones with last commits at a full commit id. | ||
| 37 | func TestGetTreeLastCommitCache(t *testing.T) { | 37 | func TestGetTreeLastCommitCache(t *testing.T) { | ||
| 38 | + | t.Parallel() | |||
| 38 | ctx := context.Background() | 39 | ctx := context.Background() | ||
| 39 | pub := domain.Repo{Name: name(t, "pub"), Visibility: domain.VisibilityPublic} | 40 | pub := domain.Repo{Name: name(t, "pub"), Visibility: domain.VisibilityPublic} | ||
| 40 | full := domain.RevisionOf(oid("a")) | 41 | full := domain.RevisionOf(oid("a")) | ||
| @@ -55,6 +56,7 @@ func TestGetTreeLastCommitCache(t *testing.T) { | |||||
| 55 | } | 56 | } | ||
| 56 | for _, tc := range tests { | 57 | for _, tc := range tests { | ||
| 57 | t.Run(tc.name, func(t *testing.T) { | 58 | t.Run(tc.name, func(t *testing.T) { | ||
| 59 | + | t.Parallel() | |||
| 58 | f := newBrowseFixture(t) | 60 | f := newBrowseFixture(t) | ||
| 59 | f.meta.EXPECT().Get(mock.Anything, pub.Name).Return(pub, nil) | 61 | f.meta.EXPECT().Get(mock.Anything, pub.Name).Return(pub, nil) | ||
| 60 | f.reader.EXPECT().GetTree(mock.Anything, pub.Name, tc.rev, dir, tc.includeLast). | 62 | f.reader.EXPECT().GetTree(mock.Anything, pub.Name, tc.rev, dir, tc.includeLast). | ||
| @@ -69,6 +71,7 @@ func TestGetTreeLastCommitCache(t *testing.T) { | |||||
| 69 | } | 71 | } | ||
| 70 | 72 | ||||
| 71 | func TestGetTreeLastCommitCacheKey(t *testing.T) { | 73 | func TestGetTreeLastCommitCacheKey(t *testing.T) { | ||
| 74 | + | t.Parallel() | |||
| 72 | ctx := context.Background() | 75 | ctx := context.Background() | ||
| 73 | pubA := domain.Repo{Name: name(t, "a"), Visibility: domain.VisibilityPublic} | 76 | pubA := domain.Repo{Name: name(t, "a"), Visibility: domain.VisibilityPublic} | ||
| 74 | pubB := domain.Repo{Name: name(t, "b"), Visibility: domain.VisibilityPublic} | 77 | pubB := domain.Repo{Name: name(t, "b"), Visibility: domain.VisibilityPublic} | ||
| @@ -102,6 +105,7 @@ func TestGetTreeLastCommitCacheKey(t *testing.T) { | |||||
| 102 | // TestGetTreeCacheKeepsAccessRule: a hit never skips the access check, and an | 105 | // TestGetTreeCacheKeepsAccessRule: a hit never skips the access check, and an | ||
| 103 | // error is not cached. | 106 | // error is not cached. | ||
| 104 | func TestGetTreeCacheKeepsAccessRule(t *testing.T) { | 107 | func TestGetTreeCacheKeepsAccessRule(t *testing.T) { | ||
| 108 | + | t.Parallel() | |||
| 105 | ctx := context.Background() | 109 | ctx := context.Background() | ||
| 106 | priv := domain.Repo{Name: name(t, "priv"), Visibility: domain.VisibilityPrivate} | 110 | priv := domain.Repo{Name: name(t, "priv"), Visibility: domain.VisibilityPrivate} | ||
| 107 | rev, dir := domain.RevisionOf(oid("a")), treePath(t, "d") | 111 | rev, dir := domain.RevisionOf(oid("a")), treePath(t, "d") | ||
| @@ -122,6 +126,7 @@ func TestGetTreeCacheKeepsAccessRule(t *testing.T) { | |||||
| 122 | // TestGetTreeCacheReturnsCopies: a caller that edits its tree does not alter | 126 | // TestGetTreeCacheReturnsCopies: a caller that edits its tree does not alter | ||
| 123 | // what the next one gets. | 127 | // what the next one gets. | ||
| 124 | func TestGetTreeCacheReturnsCopies(t *testing.T) { | 128 | func TestGetTreeCacheReturnsCopies(t *testing.T) { | ||
| 129 | + | t.Parallel() | |||
| 125 | ctx := context.Background() | 130 | ctx := context.Background() | ||
| 126 | pub := domain.Repo{Name: name(t, "pub"), Visibility: domain.VisibilityPublic} | 131 | pub := domain.Repo{Name: name(t, "pub"), Visibility: domain.VisibilityPublic} | ||
| 127 | rev, dir := domain.RevisionOf(oid("a")), treePath(t, "d") | 132 | rev, dir := domain.RevisionOf(oid("a")), treePath(t, "d") | ||
| @@ -143,6 +148,7 @@ func TestGetTreeCacheReturnsCopies(t *testing.T) { | |||||
| 143 | // TestHistoryCacheBounds: the entry bound and the byte bound evict the least | 148 | // TestHistoryCacheBounds: the entry bound and the byte bound evict the least | ||
| 144 | // recently used answer. | 149 | // recently used answer. | ||
| 145 | func TestHistoryCacheBounds(t *testing.T) { | 150 | func TestHistoryCacheBounds(t *testing.T) { | ||
| 151 | + | t.Parallel() | |||
| 146 | ctx := context.Background() | 152 | ctx := context.Background() | ||
| 147 | pub := domain.Repo{Name: name(t, "pub"), Visibility: domain.VisibilityPublic} | 153 | pub := domain.Repo{Name: name(t, "pub"), Visibility: domain.VisibilityPublic} | ||
| 148 | paths := []string{"p0", "p1", "p2"} | 154 | paths := []string{"p0", "p1", "p2"} | ||
| @@ -167,6 +173,7 @@ func TestHistoryCacheBounds(t *testing.T) { | |||||
| 167 | } | 173 | } | ||
| 168 | for _, tc := range tests { | 174 | for _, tc := range tests { | ||
| 169 | t.Run(tc.name, func(t *testing.T) { | 175 | t.Run(tc.name, func(t *testing.T) { | ||
| 176 | + | t.Parallel() | |||
| 170 | f := newBrowseFixture(t) | 177 | f := newBrowseFixture(t) | ||
| 171 | f.browse.WithHistoryCache(tc.entries, tc.bytes) | 178 | f.browse.WithHistoryCache(tc.entries, tc.bytes) | ||
| 172 | f.meta.EXPECT().Get(mock.Anything, pub.Name).Return(pub, nil) | 179 | f.meta.EXPECT().Get(mock.Anything, pub.Name).Return(pub, nil) | ||
| @@ -212,6 +219,7 @@ func TestHistoryCacheBounds(t *testing.T) { | |||||
| 212 | // TestListCommitsLastChangeCache: the latest commit of a path at a full | 219 | // TestListCommitsLastChangeCache: the latest commit of a path at a full | ||
| 213 | // commit id is cached; every other page shape is not. | 220 | // commit id is cached; every other page shape is not. | ||
| 214 | func TestListCommitsLastChangeCache(t *testing.T) { | 221 | func TestListCommitsLastChangeCache(t *testing.T) { | ||
| 222 | + | t.Parallel() | |||
| 215 | ctx := context.Background() | 223 | ctx := context.Background() | ||
| 216 | pub := domain.Repo{Name: name(t, "pub"), Visibility: domain.VisibilityPublic} | 224 | pub := domain.Repo{Name: name(t, "pub"), Visibility: domain.VisibilityPublic} | ||
| 217 | full := domain.RevisionOf(oid("a")) | 225 | full := domain.RevisionOf(oid("a")) | ||
| @@ -234,6 +242,7 @@ func TestListCommitsLastChangeCache(t *testing.T) { | |||||
| 234 | } | 242 | } | ||
| 235 | for _, tc := range tests { | 243 | for _, tc := range tests { | ||
| 236 | t.Run(tc.name, func(t *testing.T) { | 244 | t.Run(tc.name, func(t *testing.T) { | ||
| 245 | + | t.Parallel() | |||
| 237 | f := newBrowseFixture(t) | 246 | f := newBrowseFixture(t) | ||
| 238 | f.meta.EXPECT().Get(mock.Anything, pub.Name).Return(pub, nil) | 247 | f.meta.EXPECT().Get(mock.Anything, pub.Name).Return(pub, nil) | ||
| 239 | q := ports.CommitQuery{Revision: tc.rev, Path: tc.path, Limit: tc.size} | 248 | q := ports.CommitQuery{Revision: tc.rev, Path: tc.path, Limit: tc.size} | ||
| @@ -252,6 +261,7 @@ func TestListCommitsLastChangeCache(t *testing.T) { | |||||
| 252 | } | 261 | } | ||
| 253 | 262 | ||||
| 254 | t.Run("a following page is not cached", func(t *testing.T) { | 263 | t.Run("a following page is not cached", func(t *testing.T) { | ||
| 264 | + | t.Parallel() | |||
| 255 | f := newBrowseFixture(t) | 265 | f := newBrowseFixture(t) | ||
| 256 | f.meta.EXPECT().Get(mock.Anything, pub.Name).Return(pub, nil) | 266 | f.meta.EXPECT().Get(mock.Anything, pub.Name).Return(pub, nil) | ||
| 257 | f.reader.EXPECT().ListCommits(mock.Anything, pub.Name, ports.CommitQuery{Revision: full, Path: file, Limit: 1}). | 267 | f.reader.EXPECT().ListCommits(mock.Anything, pub.Name, ports.CommitQuery{Revision: full, Path: file, Limit: 1}). | ||
| @@ -269,6 +279,7 @@ func TestListCommitsLastChangeCache(t *testing.T) { | |||||
| 269 | } | 279 | } | ||
| 270 | 280 | ||||
| 271 | func TestHistoryCacheConcurrent(t *testing.T) { | 281 | func TestHistoryCacheConcurrent(t *testing.T) { | ||
| 282 | + | t.Parallel() | |||
| 272 | ctx := context.Background() | 283 | ctx := context.Background() | ||
| 273 | pub := domain.Repo{Name: name(t, "pub"), Visibility: domain.VisibilityPublic} | 284 | pub := domain.Repo{Name: name(t, "pub"), Visibility: domain.VisibilityPublic} | ||
| 274 | f := newBrowseFixture(t) | 285 | f := newBrowseFixture(t) | ||
M internal/app/events_test.go +33 -0
| @@ -142,6 +142,7 @@ func (w *watcher) quiet(t *testing.T, r *eventRig, n domain.RepoName) { | |||
| 142 | 142 | } | |
| 143 | 143 | ||
| 144 | 144 | func TestWatchLiveAndFiltered(t *testing.T) { | |
| 145 | + | t.Parallel() | |
| 145 | 146 | synctest.Test(t, func(t *testing.T) { | |
| 146 | 147 | r := newEventRig(t, 0) | |
| 147 | 148 | pub := r.repo("pub", domain.VisibilityPublic) | |
| @@ -177,6 +178,7 @@ func TestWatchLiveAndFiltered(t *testing.T) { | |||
| 177 | 178 | } | |
| 178 | 179 | ||
| 179 | 180 | func TestWatchRechecksAccessPerEvent(t *testing.T) { | |
| 181 | + | t.Parallel() | |
| 180 | 182 | synctest.Test(t, func(t *testing.T) { | |
| 181 | 183 | r := newEventRig(t, 0) | |
| 182 | 184 | n := name(t, "flip") | |
| @@ -205,6 +207,7 @@ func TestWatchRechecksAccessPerEvent(t *testing.T) { | |||
| 205 | 207 | } | |
| 206 | 208 | ||
| 207 | 209 | func TestWatchResumeAndGap(t *testing.T) { | |
| 210 | + | t.Parallel() | |
| 208 | 211 | const size = 8 | |
| 209 | 212 | // Each subtest gets its own bubble and a fresh run of 20 events recorded | |
| 210 | 213 | // through a first watcher; the ring then holds the last 8. | |
| @@ -227,6 +230,7 @@ func TestWatchResumeAndGap(t *testing.T) { | |||
| 227 | 230 | } | |
| 228 | 231 | ||
| 229 | 232 | t.Run("an id that fell out of the buffer gives a gap, then the oldest buffered event", func(t *testing.T) { | |
| 233 | + | t.Parallel() | |
| 230 | 234 | synctest.Test(t, func(t *testing.T) { | |
| 231 | 235 | r, _, ctx, ids := setup(t) | |
| 232 | 236 | // 20 events were taken, the buffer holds the last 8: ids[12] is the | |
| @@ -242,6 +246,7 @@ func TestWatchResumeAndGap(t *testing.T) { | |||
| 242 | 246 | }) | |
| 243 | 247 | ||
| 244 | 248 | t.Run("an unknown id gives a gap", func(t *testing.T) { | |
| 249 | + | t.Parallel() | |
| 245 | 250 | synctest.Test(t, func(t *testing.T) { | |
| 246 | 251 | r, _, ctx, _ := setup(t) | |
| 247 | 252 | for _, id := range []string{"nonsense", "zz-1", "-", "0-0"} { | |
| @@ -252,6 +257,7 @@ func TestWatchResumeAndGap(t *testing.T) { | |||
| 252 | 257 | }) | |
| 253 | 258 | ||
| 254 | 259 | t.Run("an id of a later run is a gap, not a newer event", func(t *testing.T) { | |
| 260 | + | t.Parallel() | |
| 255 | 261 | synctest.Test(t, func(t *testing.T) { | |
| 256 | 262 | r, _, ctx, _ := setup(t) | |
| 257 | 263 | w := r.watch(ctx, domain.Root(), app.WatchParams{ResumeAfter: "otherboot-999"}) | |
| @@ -260,6 +266,7 @@ func TestWatchResumeAndGap(t *testing.T) { | |||
| 260 | 266 | }) | |
| 261 | 267 | ||
| 262 | 268 | t.Run("resume replays the buffered events after the id, then goes live", func(t *testing.T) { | |
| 269 | + | t.Parallel() | |
| 263 | 270 | synctest.Test(t, func(t *testing.T) { | |
| 264 | 271 | r, n, ctx, ids := setup(t) | |
| 265 | 272 | w := r.watch(ctx, domain.Root(), app.WatchParams{ResumeAfter: ids[16]}) | |
| @@ -277,6 +284,7 @@ func TestWatchResumeAndGap(t *testing.T) { | |||
| 277 | 284 | }) | |
| 278 | 285 | ||
| 279 | 286 | t.Run("resume at the last event replays nothing, then goes live", func(t *testing.T) { | |
| 287 | + | t.Parallel() | |
| 280 | 288 | synctest.Test(t, func(t *testing.T) { | |
| 281 | 289 | r, n, ctx, ids := setup(t) | |
| 282 | 290 | w := r.watch(ctx, domain.Root(), app.WatchParams{ResumeAfter: ids[len(ids)-1]}) | |
| @@ -289,6 +297,7 @@ func TestWatchResumeAndGap(t *testing.T) { | |||
| 289 | 297 | } | |
| 290 | 298 | ||
| 291 | 299 | func TestWatchTopicErrors(t *testing.T) { | |
| 300 | + | t.Parallel() | |
| 292 | 301 | r := newEventRig(t, 0) | |
| 293 | 302 | r.repo("pub", domain.VisibilityPublic) | |
| 294 | 303 | r.repo("priv", domain.VisibilityPrivate) | |
| @@ -310,7 +319,9 @@ func TestWatchTopicErrors(t *testing.T) { | |||
| 310 | 319 | } | |
| 311 | 320 | ||
| 312 | 321 | func TestWatchEndsCleanly(t *testing.T) { | |
| 322 | + | t.Parallel() | |
| 313 | 323 | t.Run("on context cancellation", func(t *testing.T) { | |
| 324 | + | t.Parallel() | |
| 314 | 325 | synctest.Test(t, func(t *testing.T) { | |
| 315 | 326 | r := newEventRig(t, 0) | |
| 316 | 327 | ctx, cancel := context.WithCancel(context.Background()) | |
| @@ -330,6 +341,7 @@ func TestWatchEndsCleanly(t *testing.T) { | |||
| 330 | 341 | }) | |
| 331 | 342 | ||
| 332 | 343 | t.Run("on Close", func(t *testing.T) { | |
| 344 | + | t.Parallel() | |
| 333 | 345 | synctest.Test(t, func(t *testing.T) { | |
| 334 | 346 | r := newEventRig(t, 0) | |
| 335 | 347 | w := r.watch(context.Background(), domain.Root(), app.WatchParams{}) | |
| @@ -348,6 +360,7 @@ func TestWatchEndsCleanly(t *testing.T) { | |||
| 348 | 360 | }) | |
| 349 | 361 | ||
| 350 | 362 | t.Run("a failing send ends the watch with its error", func(t *testing.T) { | |
| 363 | + | t.Parallel() | |
| 351 | 364 | synctest.Test(t, func(t *testing.T) { | |
| 352 | 365 | r := newEventRig(t, 0) | |
| 353 | 366 | n := r.repo("pub", domain.VisibilityPublic) | |
| @@ -365,6 +378,7 @@ func TestWatchEndsCleanly(t *testing.T) { | |||
| 365 | 378 | } | |
| 366 | 379 | ||
| 367 | 380 | func TestWatchSlowWatcherGetsAGap(t *testing.T) { | |
| 381 | + | t.Parallel() | |
| 368 | 382 | synctest.Test(t, func(t *testing.T) { | |
| 369 | 383 | const size = 4 | |
| 370 | 384 | r := newEventRig(t, size) | |
| @@ -423,6 +437,7 @@ func TestWatchSlowWatcherGetsAGap(t *testing.T) { | |||
| 423 | 437 | } | |
| 424 | 438 | ||
| 425 | 439 | func TestEventsRepoPayloads(t *testing.T) { | |
| 440 | + | t.Parallel() | |
| 426 | 441 | synctest.Test(t, func(t *testing.T) { | |
| 427 | 442 | r := newEventRig(t, 0) | |
| 428 | 443 | pub := r.repo("pub", domain.VisibilityPublic) | |
| @@ -468,6 +483,7 @@ func tokenWith(id string, expires time.Time, repos ...string) domain.Principal { | |||
| 468 | 483 | } | |
| 469 | 484 | ||
| 470 | 485 | func TestWatchEndsWhenTheTokenIsRevoked(t *testing.T) { | |
| 486 | + | t.Parallel() | |
| 471 | 487 | synctest.Test(t, func(t *testing.T) { | |
| 472 | 488 | // Asking at every turn keeps the test independent of the clock. | |
| 473 | 489 | r := newEventRigConfig(t, app.EventsConfig{RevalidateEvery: -1}) | |
| @@ -501,6 +517,7 @@ func TestWatchEndsWhenTheTokenIsRevoked(t *testing.T) { | |||
| 501 | 517 | } | |
| 502 | 518 | ||
| 503 | 519 | func TestWatchEndsWhenTheTokenExpires(t *testing.T) { | |
| 520 | + | t.Parallel() | |
| 504 | 521 | synctest.Test(t, func(t *testing.T) { | |
| 505 | 522 | r := newEventRig(t, 0) | |
| 506 | 523 | pub := r.repo("pub", domain.VisibilityPublic) | |
| @@ -520,7 +537,9 @@ func TestWatchEndsWhenTheTokenExpires(t *testing.T) { | |||
| 520 | 537 | } | |
| 521 | 538 | ||
| 522 | 539 | func TestWatchRevalidatesIdleWatchersAndCachesTheAnswer(t *testing.T) { | |
| 540 | + | t.Parallel() | |
| 523 | 541 | t.Run("a quiet forge still notices a revocation", func(t *testing.T) { | |
| 542 | + | t.Parallel() | |
| 524 | 543 | synctest.Test(t, func(t *testing.T) { | |
| 525 | 544 | r := newEventRigConfig(t, app.EventsConfig{}) // the production default interval | |
| 526 | 545 | w := r.watch(context.Background(), tokenWith("tok", time.Time{}), app.WatchParams{}) | |
| @@ -546,6 +565,7 @@ func TestWatchRevalidatesIdleWatchersAndCachesTheAnswer(t *testing.T) { | |||
| 546 | 565 | }) | |
| 547 | 566 | ||
| 548 | 567 | t.Run("the answer is kept between checks", func(t *testing.T) { | |
| 568 | + | t.Parallel() | |
| 549 | 569 | synctest.Test(t, func(t *testing.T) { | |
| 550 | 570 | r := newEventRigConfig(t, app.EventsConfig{RevalidateEvery: time.Hour}) | |
| 551 | 571 | pub := r.repo("pub", domain.VisibilityPublic) | |
| @@ -568,6 +588,7 @@ func TestWatchRevalidatesIdleWatchersAndCachesTheAnswer(t *testing.T) { | |||
| 568 | 588 | }) | |
| 569 | 589 | ||
| 570 | 590 | t.Run("root and anonymous never ask", func(t *testing.T) { | |
| 591 | + | t.Parallel() | |
| 571 | 592 | synctest.Test(t, func(t *testing.T) { | |
| 572 | 593 | r := newEventRigConfig(t, app.EventsConfig{RevalidateEvery: -1}) | |
| 573 | 594 | pub := r.repo("pub", domain.VisibilityPublic) | |
| @@ -584,9 +605,11 @@ func TestWatchRevalidatesIdleWatchersAndCachesTheAnswer(t *testing.T) { | |||
| 584 | 605 | } | |
| 585 | 606 | ||
| 586 | 607 | func TestWatchLimits(t *testing.T) { | |
| 608 | + | t.Parallel() | |
| 587 | 609 | noop := func(app.WatchEvent) error { return nil } | |
| 588 | 610 | ||
| 589 | 611 | t.Run("too many topics", func(t *testing.T) { | |
| 612 | + | t.Parallel() | |
| 590 | 613 | synctest.Test(t, func(t *testing.T) { | |
| 591 | 614 | r := newEventRig(t, 0) | |
| 592 | 615 | r.repo("pub", domain.VisibilityPublic) | |
| @@ -603,6 +626,7 @@ func TestWatchLimits(t *testing.T) { | |||
| 603 | 626 | }) | |
| 604 | 627 | ||
| 605 | 628 | t.Run("total watchers", func(t *testing.T) { | |
| 629 | + | t.Parallel() | |
| 606 | 630 | synctest.Test(t, func(t *testing.T) { | |
| 607 | 631 | r := newEventRig(t, 0) | |
| 608 | 632 | ctx, cancel := context.WithCancel(context.Background()) | |
| @@ -626,6 +650,7 @@ func TestWatchLimits(t *testing.T) { | |||
| 626 | 650 | }) | |
| 627 | 651 | ||
| 628 | 652 | t.Run("watchers per client", func(t *testing.T) { | |
| 653 | + | t.Parallel() | |
| 629 | 654 | synctest.Test(t, func(t *testing.T) { | |
| 630 | 655 | r := newEventRig(t, 0) | |
| 631 | 656 | ctx, cancel := context.WithCancel(context.Background()) | |
| @@ -679,7 +704,9 @@ func (r *eventRig) liveRepo(s string, vis domain.Visibility, created time.Time) | |||
| 679 | 704 | } | |
| 680 | 705 | ||
| 681 | 706 | func TestEventsKeepTheVisibilityOfTheirTime(t *testing.T) { | |
| 707 | + | t.Parallel() | |
| 682 | 708 | t.Run("a repository made public does not reveal what happened while it was private", func(t *testing.T) { | |
| 709 | + | t.Parallel() | |
| 683 | 710 | synctest.Test(t, func(t *testing.T) { | |
| 684 | 711 | ctx, cancel := context.WithCancel(context.Background()) | |
| 685 | 712 | defer cancel() | |
| @@ -713,6 +740,7 @@ func TestEventsKeepTheVisibilityOfTheirTime(t *testing.T) { | |||
| 713 | 740 | }) | |
| 714 | 741 | ||
| 715 | 742 | t.Run("a repository deleted and created again does not inherit old events", func(t *testing.T) { | |
| 743 | + | t.Parallel() | |
| 716 | 744 | synctest.Test(t, func(t *testing.T) { | |
| 717 | 745 | ctx, cancel := context.WithCancel(context.Background()) | |
| 718 | 746 | defer cancel() | |
| @@ -739,6 +767,7 @@ func TestEventsKeepTheVisibilityOfTheirTime(t *testing.T) { | |||
| 739 | 767 | }) | |
| 740 | 768 | ||
| 741 | 769 | t.Run("a repository made private tells those who could read it, once", func(t *testing.T) { | |
| 770 | + | t.Parallel() | |
| 742 | 771 | synctest.Test(t, func(t *testing.T) { | |
| 743 | 772 | ctx, cancel := context.WithCancel(context.Background()) | |
| 744 | 773 | defer cancel() | |
| @@ -774,6 +803,7 @@ func TestEventsKeepTheVisibilityOfTheirTime(t *testing.T) { | |||
| 774 | 803 | }) | |
| 775 | 804 | ||
| 776 | 805 | t.Run("a private repository that never was public stays hidden", func(t *testing.T) { | |
| 806 | + | t.Parallel() | |
| 777 | 807 | synctest.Test(t, func(t *testing.T) { | |
| 778 | 808 | ctx, cancel := context.WithCancel(context.Background()) | |
| 779 | 809 | defer cancel() | |
| @@ -792,6 +822,7 @@ func TestEventsKeepTheVisibilityOfTheirTime(t *testing.T) { | |||
| 792 | 822 | // TestPushEventsKeepTheirSnapshot: a push is told by the repository as it was | |
| 793 | 823 | // when published, not as the store says when the feeder gets to it. | |
| 794 | 824 | func TestPushEventsKeepTheirSnapshot(t *testing.T) { | |
| 825 | + | t.Parallel() | |
| 795 | 826 | tests := []struct { | |
| 796 | 827 | name string | |
| 797 | 828 | swap func(repo *domain.Repo) | |
| @@ -805,6 +836,7 @@ func TestPushEventsKeepTheirSnapshot(t *testing.T) { | |||
| 805 | 836 | } | |
| 806 | 837 | for _, tt := range tests { | |
| 807 | 838 | t.Run(tt.name, func(t *testing.T) { | |
| 839 | + | t.Parallel() | |
| 808 | 840 | synctest.Test(t, func(t *testing.T) { | |
| 809 | 841 | ctx, cancel := context.WithCancel(context.Background()) | |
| 810 | 842 | defer cancel() | |
| @@ -835,6 +867,7 @@ func TestPushEventsKeepTheirSnapshot(t *testing.T) { | |||
| 835 | 867 | } | |
| 836 | 868 | ||
| 837 | 869 | t.Run("a push of an unknown repository is told to nobody", func(t *testing.T) { | |
| 870 | + | t.Parallel() | |
| 838 | 871 | synctest.Test(t, func(t *testing.T) { | |
| 839 | 872 | ctx, cancel := context.WithCancel(context.Background()) | |
| 840 | 873 | defer cancel() | |
| @@ -142,6 +142,7 @@ func (w *watcher) quiet(t *testing.T, r *eventRig, n domain.RepoName) { | |||||
| 142 | } | 142 | } | ||
| 143 | 143 | ||||
| 144 | func TestWatchLiveAndFiltered(t *testing.T) { | 144 | func TestWatchLiveAndFiltered(t *testing.T) { | ||
| 145 | + | t.Parallel() | |||
| 145 | synctest.Test(t, func(t *testing.T) { | 146 | synctest.Test(t, func(t *testing.T) { | ||
| 146 | r := newEventRig(t, 0) | 147 | r := newEventRig(t, 0) | ||
| 147 | pub := r.repo("pub", domain.VisibilityPublic) | 148 | pub := r.repo("pub", domain.VisibilityPublic) | ||
| @@ -177,6 +178,7 @@ func TestWatchLiveAndFiltered(t *testing.T) { | |||||
| 177 | } | 178 | } | ||
| 178 | 179 | ||||
| 179 | func TestWatchRechecksAccessPerEvent(t *testing.T) { | 180 | func TestWatchRechecksAccessPerEvent(t *testing.T) { | ||
| 181 | + | t.Parallel() | |||
| 180 | synctest.Test(t, func(t *testing.T) { | 182 | synctest.Test(t, func(t *testing.T) { | ||
| 181 | r := newEventRig(t, 0) | 183 | r := newEventRig(t, 0) | ||
| 182 | n := name(t, "flip") | 184 | n := name(t, "flip") | ||
| @@ -205,6 +207,7 @@ func TestWatchRechecksAccessPerEvent(t *testing.T) { | |||||
| 205 | } | 207 | } | ||
| 206 | 208 | ||||
| 207 | func TestWatchResumeAndGap(t *testing.T) { | 209 | func TestWatchResumeAndGap(t *testing.T) { | ||
| 210 | + | t.Parallel() | |||
| 208 | const size = 8 | 211 | const size = 8 | ||
| 209 | // Each subtest gets its own bubble and a fresh run of 20 events recorded | 212 | // Each subtest gets its own bubble and a fresh run of 20 events recorded | ||
| 210 | // through a first watcher; the ring then holds the last 8. | 213 | // through a first watcher; the ring then holds the last 8. | ||
| @@ -227,6 +230,7 @@ func TestWatchResumeAndGap(t *testing.T) { | |||||
| 227 | } | 230 | } | ||
| 228 | 231 | ||||
| 229 | t.Run("an id that fell out of the buffer gives a gap, then the oldest buffered event", func(t *testing.T) { | 232 | t.Run("an id that fell out of the buffer gives a gap, then the oldest buffered event", func(t *testing.T) { | ||
| 233 | + | t.Parallel() | |||
| 230 | synctest.Test(t, func(t *testing.T) { | 234 | synctest.Test(t, func(t *testing.T) { | ||
| 231 | r, _, ctx, ids := setup(t) | 235 | r, _, ctx, ids := setup(t) | ||
| 232 | // 20 events were taken, the buffer holds the last 8: ids[12] is the | 236 | // 20 events were taken, the buffer holds the last 8: ids[12] is the | ||
| @@ -242,6 +246,7 @@ func TestWatchResumeAndGap(t *testing.T) { | |||||
| 242 | }) | 246 | }) | ||
| 243 | 247 | ||||
| 244 | t.Run("an unknown id gives a gap", func(t *testing.T) { | 248 | t.Run("an unknown id gives a gap", func(t *testing.T) { | ||
| 249 | + | t.Parallel() | |||
| 245 | synctest.Test(t, func(t *testing.T) { | 250 | synctest.Test(t, func(t *testing.T) { | ||
| 246 | r, _, ctx, _ := setup(t) | 251 | r, _, ctx, _ := setup(t) | ||
| 247 | for _, id := range []string{"nonsense", "zz-1", "-", "0-0"} { | 252 | for _, id := range []string{"nonsense", "zz-1", "-", "0-0"} { | ||
| @@ -252,6 +257,7 @@ func TestWatchResumeAndGap(t *testing.T) { | |||||
| 252 | }) | 257 | }) | ||
| 253 | 258 | ||||
| 254 | t.Run("an id of a later run is a gap, not a newer event", func(t *testing.T) { | 259 | t.Run("an id of a later run is a gap, not a newer event", func(t *testing.T) { | ||
| 260 | + | t.Parallel() | |||
| 255 | synctest.Test(t, func(t *testing.T) { | 261 | synctest.Test(t, func(t *testing.T) { | ||
| 256 | r, _, ctx, _ := setup(t) | 262 | r, _, ctx, _ := setup(t) | ||
| 257 | w := r.watch(ctx, domain.Root(), app.WatchParams{ResumeAfter: "otherboot-999"}) | 263 | w := r.watch(ctx, domain.Root(), app.WatchParams{ResumeAfter: "otherboot-999"}) | ||
| @@ -260,6 +266,7 @@ func TestWatchResumeAndGap(t *testing.T) { | |||||
| 260 | }) | 266 | }) | ||
| 261 | 267 | ||||
| 262 | t.Run("resume replays the buffered events after the id, then goes live", func(t *testing.T) { | 268 | t.Run("resume replays the buffered events after the id, then goes live", func(t *testing.T) { | ||
| 269 | + | t.Parallel() | |||
| 263 | synctest.Test(t, func(t *testing.T) { | 270 | synctest.Test(t, func(t *testing.T) { | ||
| 264 | r, n, ctx, ids := setup(t) | 271 | r, n, ctx, ids := setup(t) | ||
| 265 | w := r.watch(ctx, domain.Root(), app.WatchParams{ResumeAfter: ids[16]}) | 272 | w := r.watch(ctx, domain.Root(), app.WatchParams{ResumeAfter: ids[16]}) | ||
| @@ -277,6 +284,7 @@ func TestWatchResumeAndGap(t *testing.T) { | |||||
| 277 | }) | 284 | }) | ||
| 278 | 285 | ||||
| 279 | t.Run("resume at the last event replays nothing, then goes live", func(t *testing.T) { | 286 | t.Run("resume at the last event replays nothing, then goes live", func(t *testing.T) { | ||
| 287 | + | t.Parallel() | |||
| 280 | synctest.Test(t, func(t *testing.T) { | 288 | synctest.Test(t, func(t *testing.T) { | ||
| 281 | r, n, ctx, ids := setup(t) | 289 | r, n, ctx, ids := setup(t) | ||
| 282 | w := r.watch(ctx, domain.Root(), app.WatchParams{ResumeAfter: ids[len(ids)-1]}) | 290 | w := r.watch(ctx, domain.Root(), app.WatchParams{ResumeAfter: ids[len(ids)-1]}) | ||
| @@ -289,6 +297,7 @@ func TestWatchResumeAndGap(t *testing.T) { | |||||
| 289 | } | 297 | } | ||
| 290 | 298 | ||||
| 291 | func TestWatchTopicErrors(t *testing.T) { | 299 | func TestWatchTopicErrors(t *testing.T) { | ||
| 300 | + | t.Parallel() | |||
| 292 | r := newEventRig(t, 0) | 301 | r := newEventRig(t, 0) | ||
| 293 | r.repo("pub", domain.VisibilityPublic) | 302 | r.repo("pub", domain.VisibilityPublic) | ||
| 294 | r.repo("priv", domain.VisibilityPrivate) | 303 | r.repo("priv", domain.VisibilityPrivate) | ||
| @@ -310,7 +319,9 @@ func TestWatchTopicErrors(t *testing.T) { | |||||
| 310 | } | 319 | } | ||
| 311 | 320 | ||||
| 312 | func TestWatchEndsCleanly(t *testing.T) { | 321 | func TestWatchEndsCleanly(t *testing.T) { | ||
| 322 | + | t.Parallel() | |||
| 313 | t.Run("on context cancellation", func(t *testing.T) { | 323 | t.Run("on context cancellation", func(t *testing.T) { | ||
| 324 | + | t.Parallel() | |||
| 314 | synctest.Test(t, func(t *testing.T) { | 325 | synctest.Test(t, func(t *testing.T) { | ||
| 315 | r := newEventRig(t, 0) | 326 | r := newEventRig(t, 0) | ||
| 316 | ctx, cancel := context.WithCancel(context.Background()) | 327 | ctx, cancel := context.WithCancel(context.Background()) | ||
| @@ -330,6 +341,7 @@ func TestWatchEndsCleanly(t *testing.T) { | |||||
| 330 | }) | 341 | }) | ||
| 331 | 342 | ||||
| 332 | t.Run("on Close", func(t *testing.T) { | 343 | t.Run("on Close", func(t *testing.T) { | ||
| 344 | + | t.Parallel() | |||
| 333 | synctest.Test(t, func(t *testing.T) { | 345 | synctest.Test(t, func(t *testing.T) { | ||
| 334 | r := newEventRig(t, 0) | 346 | r := newEventRig(t, 0) | ||
| 335 | w := r.watch(context.Background(), domain.Root(), app.WatchParams{}) | 347 | w := r.watch(context.Background(), domain.Root(), app.WatchParams{}) | ||
| @@ -348,6 +360,7 @@ func TestWatchEndsCleanly(t *testing.T) { | |||||
| 348 | }) | 360 | }) | ||
| 349 | 361 | ||||
| 350 | t.Run("a failing send ends the watch with its error", func(t *testing.T) { | 362 | t.Run("a failing send ends the watch with its error", func(t *testing.T) { | ||
| 363 | + | t.Parallel() | |||
| 351 | synctest.Test(t, func(t *testing.T) { | 364 | synctest.Test(t, func(t *testing.T) { | ||
| 352 | r := newEventRig(t, 0) | 365 | r := newEventRig(t, 0) | ||
| 353 | n := r.repo("pub", domain.VisibilityPublic) | 366 | n := r.repo("pub", domain.VisibilityPublic) | ||
| @@ -365,6 +378,7 @@ func TestWatchEndsCleanly(t *testing.T) { | |||||
| 365 | } | 378 | } | ||
| 366 | 379 | ||||
| 367 | func TestWatchSlowWatcherGetsAGap(t *testing.T) { | 380 | func TestWatchSlowWatcherGetsAGap(t *testing.T) { | ||
| 381 | + | t.Parallel() | |||
| 368 | synctest.Test(t, func(t *testing.T) { | 382 | synctest.Test(t, func(t *testing.T) { | ||
| 369 | const size = 4 | 383 | const size = 4 | ||
| 370 | r := newEventRig(t, size) | 384 | r := newEventRig(t, size) | ||
| @@ -423,6 +437,7 @@ func TestWatchSlowWatcherGetsAGap(t *testing.T) { | |||||
| 423 | } | 437 | } | ||
| 424 | 438 | ||||
| 425 | func TestEventsRepoPayloads(t *testing.T) { | 439 | func TestEventsRepoPayloads(t *testing.T) { | ||
| 440 | + | t.Parallel() | |||
| 426 | synctest.Test(t, func(t *testing.T) { | 441 | synctest.Test(t, func(t *testing.T) { | ||
| 427 | r := newEventRig(t, 0) | 442 | r := newEventRig(t, 0) | ||
| 428 | pub := r.repo("pub", domain.VisibilityPublic) | 443 | pub := r.repo("pub", domain.VisibilityPublic) | ||
| @@ -468,6 +483,7 @@ func tokenWith(id string, expires time.Time, repos ...string) domain.Principal { | |||||
| 468 | } | 483 | } | ||
| 469 | 484 | ||||
| 470 | func TestWatchEndsWhenTheTokenIsRevoked(t *testing.T) { | 485 | func TestWatchEndsWhenTheTokenIsRevoked(t *testing.T) { | ||
| 486 | + | t.Parallel() | |||
| 471 | synctest.Test(t, func(t *testing.T) { | 487 | synctest.Test(t, func(t *testing.T) { | ||
| 472 | // Asking at every turn keeps the test independent of the clock. | 488 | // Asking at every turn keeps the test independent of the clock. | ||
| 473 | r := newEventRigConfig(t, app.EventsConfig{RevalidateEvery: -1}) | 489 | r := newEventRigConfig(t, app.EventsConfig{RevalidateEvery: -1}) | ||
| @@ -501,6 +517,7 @@ func TestWatchEndsWhenTheTokenIsRevoked(t *testing.T) { | |||||
| 501 | } | 517 | } | ||
| 502 | 518 | ||||
| 503 | func TestWatchEndsWhenTheTokenExpires(t *testing.T) { | 519 | func TestWatchEndsWhenTheTokenExpires(t *testing.T) { | ||
| 520 | + | t.Parallel() | |||
| 504 | synctest.Test(t, func(t *testing.T) { | 521 | synctest.Test(t, func(t *testing.T) { | ||
| 505 | r := newEventRig(t, 0) | 522 | r := newEventRig(t, 0) | ||
| 506 | pub := r.repo("pub", domain.VisibilityPublic) | 523 | pub := r.repo("pub", domain.VisibilityPublic) | ||
| @@ -520,7 +537,9 @@ func TestWatchEndsWhenTheTokenExpires(t *testing.T) { | |||||
| 520 | } | 537 | } | ||
| 521 | 538 | ||||
| 522 | func TestWatchRevalidatesIdleWatchersAndCachesTheAnswer(t *testing.T) { | 539 | func TestWatchRevalidatesIdleWatchersAndCachesTheAnswer(t *testing.T) { | ||
| 540 | + | t.Parallel() | |||
| 523 | t.Run("a quiet forge still notices a revocation", func(t *testing.T) { | 541 | t.Run("a quiet forge still notices a revocation", func(t *testing.T) { | ||
| 542 | + | t.Parallel() | |||
| 524 | synctest.Test(t, func(t *testing.T) { | 543 | synctest.Test(t, func(t *testing.T) { | ||
| 525 | r := newEventRigConfig(t, app.EventsConfig{}) // the production default interval | 544 | r := newEventRigConfig(t, app.EventsConfig{}) // the production default interval | ||
| 526 | w := r.watch(context.Background(), tokenWith("tok", time.Time{}), app.WatchParams{}) | 545 | w := r.watch(context.Background(), tokenWith("tok", time.Time{}), app.WatchParams{}) | ||
| @@ -546,6 +565,7 @@ func TestWatchRevalidatesIdleWatchersAndCachesTheAnswer(t *testing.T) { | |||||
| 546 | }) | 565 | }) | ||
| 547 | 566 | ||||
| 548 | t.Run("the answer is kept between checks", func(t *testing.T) { | 567 | t.Run("the answer is kept between checks", func(t *testing.T) { | ||
| 568 | + | t.Parallel() | |||
| 549 | synctest.Test(t, func(t *testing.T) { | 569 | synctest.Test(t, func(t *testing.T) { | ||
| 550 | r := newEventRigConfig(t, app.EventsConfig{RevalidateEvery: time.Hour}) | 570 | r := newEventRigConfig(t, app.EventsConfig{RevalidateEvery: time.Hour}) | ||
| 551 | pub := r.repo("pub", domain.VisibilityPublic) | 571 | pub := r.repo("pub", domain.VisibilityPublic) | ||
| @@ -568,6 +588,7 @@ func TestWatchRevalidatesIdleWatchersAndCachesTheAnswer(t *testing.T) { | |||||
| 568 | }) | 588 | }) | ||
| 569 | 589 | ||||
| 570 | t.Run("root and anonymous never ask", func(t *testing.T) { | 590 | t.Run("root and anonymous never ask", func(t *testing.T) { | ||
| 591 | + | t.Parallel() | |||
| 571 | synctest.Test(t, func(t *testing.T) { | 592 | synctest.Test(t, func(t *testing.T) { | ||
| 572 | r := newEventRigConfig(t, app.EventsConfig{RevalidateEvery: -1}) | 593 | r := newEventRigConfig(t, app.EventsConfig{RevalidateEvery: -1}) | ||
| 573 | pub := r.repo("pub", domain.VisibilityPublic) | 594 | pub := r.repo("pub", domain.VisibilityPublic) | ||
| @@ -584,9 +605,11 @@ func TestWatchRevalidatesIdleWatchersAndCachesTheAnswer(t *testing.T) { | |||||
| 584 | } | 605 | } | ||
| 585 | 606 | ||||
| 586 | func TestWatchLimits(t *testing.T) { | 607 | func TestWatchLimits(t *testing.T) { | ||
| 608 | + | t.Parallel() | |||
| 587 | noop := func(app.WatchEvent) error { return nil } | 609 | noop := func(app.WatchEvent) error { return nil } | ||
| 588 | 610 | ||||
| 589 | t.Run("too many topics", func(t *testing.T) { | 611 | t.Run("too many topics", func(t *testing.T) { | ||
| 612 | + | t.Parallel() | |||
| 590 | synctest.Test(t, func(t *testing.T) { | 613 | synctest.Test(t, func(t *testing.T) { | ||
| 591 | r := newEventRig(t, 0) | 614 | r := newEventRig(t, 0) | ||
| 592 | r.repo("pub", domain.VisibilityPublic) | 615 | r.repo("pub", domain.VisibilityPublic) | ||
| @@ -603,6 +626,7 @@ func TestWatchLimits(t *testing.T) { | |||||
| 603 | }) | 626 | }) | ||
| 604 | 627 | ||||
| 605 | t.Run("total watchers", func(t *testing.T) { | 628 | t.Run("total watchers", func(t *testing.T) { | ||
| 629 | + | t.Parallel() | |||
| 606 | synctest.Test(t, func(t *testing.T) { | 630 | synctest.Test(t, func(t *testing.T) { | ||
| 607 | r := newEventRig(t, 0) | 631 | r := newEventRig(t, 0) | ||
| 608 | ctx, cancel := context.WithCancel(context.Background()) | 632 | ctx, cancel := context.WithCancel(context.Background()) | ||
| @@ -626,6 +650,7 @@ func TestWatchLimits(t *testing.T) { | |||||
| 626 | }) | 650 | }) | ||
| 627 | 651 | ||||
| 628 | t.Run("watchers per client", func(t *testing.T) { | 652 | t.Run("watchers per client", func(t *testing.T) { | ||
| 653 | + | t.Parallel() | |||
| 629 | synctest.Test(t, func(t *testing.T) { | 654 | synctest.Test(t, func(t *testing.T) { | ||
| 630 | r := newEventRig(t, 0) | 655 | r := newEventRig(t, 0) | ||
| 631 | ctx, cancel := context.WithCancel(context.Background()) | 656 | ctx, cancel := context.WithCancel(context.Background()) | ||
| @@ -679,7 +704,9 @@ func (r *eventRig) liveRepo(s string, vis domain.Visibility, created time.Time) | |||||
| 679 | } | 704 | } | ||
| 680 | 705 | ||||
| 681 | func TestEventsKeepTheVisibilityOfTheirTime(t *testing.T) { | 706 | func TestEventsKeepTheVisibilityOfTheirTime(t *testing.T) { | ||
| 707 | + | t.Parallel() | |||
| 682 | t.Run("a repository made public does not reveal what happened while it was private", func(t *testing.T) { | 708 | t.Run("a repository made public does not reveal what happened while it was private", func(t *testing.T) { | ||
| 709 | + | t.Parallel() | |||
| 683 | synctest.Test(t, func(t *testing.T) { | 710 | synctest.Test(t, func(t *testing.T) { | ||
| 684 | ctx, cancel := context.WithCancel(context.Background()) | 711 | ctx, cancel := context.WithCancel(context.Background()) | ||
| 685 | defer cancel() | 712 | defer cancel() | ||
| @@ -713,6 +740,7 @@ func TestEventsKeepTheVisibilityOfTheirTime(t *testing.T) { | |||||
| 713 | }) | 740 | }) | ||
| 714 | 741 | ||||
| 715 | t.Run("a repository deleted and created again does not inherit old events", func(t *testing.T) { | 742 | t.Run("a repository deleted and created again does not inherit old events", func(t *testing.T) { | ||
| 743 | + | t.Parallel() | |||
| 716 | synctest.Test(t, func(t *testing.T) { | 744 | synctest.Test(t, func(t *testing.T) { | ||
| 717 | ctx, cancel := context.WithCancel(context.Background()) | 745 | ctx, cancel := context.WithCancel(context.Background()) | ||
| 718 | defer cancel() | 746 | defer cancel() | ||
| @@ -739,6 +767,7 @@ func TestEventsKeepTheVisibilityOfTheirTime(t *testing.T) { | |||||
| 739 | }) | 767 | }) | ||
| 740 | 768 | ||||
| 741 | t.Run("a repository made private tells those who could read it, once", func(t *testing.T) { | 769 | t.Run("a repository made private tells those who could read it, once", func(t *testing.T) { | ||
| 770 | + | t.Parallel() | |||
| 742 | synctest.Test(t, func(t *testing.T) { | 771 | synctest.Test(t, func(t *testing.T) { | ||
| 743 | ctx, cancel := context.WithCancel(context.Background()) | 772 | ctx, cancel := context.WithCancel(context.Background()) | ||
| 744 | defer cancel() | 773 | defer cancel() | ||
| @@ -774,6 +803,7 @@ func TestEventsKeepTheVisibilityOfTheirTime(t *testing.T) { | |||||
| 774 | }) | 803 | }) | ||
| 775 | 804 | ||||
| 776 | t.Run("a private repository that never was public stays hidden", func(t *testing.T) { | 805 | t.Run("a private repository that never was public stays hidden", func(t *testing.T) { | ||
| 806 | + | t.Parallel() | |||
| 777 | synctest.Test(t, func(t *testing.T) { | 807 | synctest.Test(t, func(t *testing.T) { | ||
| 778 | ctx, cancel := context.WithCancel(context.Background()) | 808 | ctx, cancel := context.WithCancel(context.Background()) | ||
| 779 | defer cancel() | 809 | defer cancel() | ||
| @@ -792,6 +822,7 @@ func TestEventsKeepTheVisibilityOfTheirTime(t *testing.T) { | |||||
| 792 | // TestPushEventsKeepTheirSnapshot: a push is told by the repository as it was | 822 | // TestPushEventsKeepTheirSnapshot: a push is told by the repository as it was | ||
| 793 | // when published, not as the store says when the feeder gets to it. | 823 | // when published, not as the store says when the feeder gets to it. | ||
| 794 | func TestPushEventsKeepTheirSnapshot(t *testing.T) { | 824 | func TestPushEventsKeepTheirSnapshot(t *testing.T) { | ||
| 825 | + | t.Parallel() | |||
| 795 | tests := []struct { | 826 | tests := []struct { | ||
| 796 | name string | 827 | name string | ||
| 797 | swap func(repo *domain.Repo) | 828 | swap func(repo *domain.Repo) | ||
| @@ -805,6 +836,7 @@ func TestPushEventsKeepTheirSnapshot(t *testing.T) { | |||||
| 805 | } | 836 | } | ||
| 806 | for _, tt := range tests { | 837 | for _, tt := range tests { | ||
| 807 | t.Run(tt.name, func(t *testing.T) { | 838 | t.Run(tt.name, func(t *testing.T) { | ||
| 839 | + | t.Parallel() | |||
| 808 | synctest.Test(t, func(t *testing.T) { | 840 | synctest.Test(t, func(t *testing.T) { | ||
| 809 | ctx, cancel := context.WithCancel(context.Background()) | 841 | ctx, cancel := context.WithCancel(context.Background()) | ||
| 810 | defer cancel() | 842 | defer cancel() | ||
| @@ -835,6 +867,7 @@ func TestPushEventsKeepTheirSnapshot(t *testing.T) { | |||||
| 835 | } | 867 | } | ||
| 836 | 868 | ||||
| 837 | t.Run("a push of an unknown repository is told to nobody", func(t *testing.T) { | 869 | t.Run("a push of an unknown repository is told to nobody", func(t *testing.T) { | ||
| 870 | + | t.Parallel() | |||
| 838 | synctest.Test(t, func(t *testing.T) { | 871 | synctest.Test(t, func(t *testing.T) { | ||
| 839 | ctx, cancel := context.WithCancel(context.Background()) | 872 | ctx, cancel := context.WithCancel(context.Background()) | ||
| 840 | defer cancel() | 873 | defer cancel() | ||
M internal/app/maintenance_test.go +9 -0
| @@ -25,6 +25,7 @@ import ( | |||
| 25 | 25 | // hangs is reported as a bubble deadlock instead of a timeout. | |
| 26 | 26 | ||
| 27 | 27 | func TestMaintainerDebounces(t *testing.T) { | |
| 28 | + | t.Parallel() | |
| 28 | 29 | synctest.Test(t, func(t *testing.T) { | |
| 29 | 30 | ctx := context.Background() | |
| 30 | 31 | n := name(t, "tools") | |
| @@ -56,6 +57,7 @@ func TestMaintainerDebounces(t *testing.T) { | |||
| 56 | 57 | } | |
| 57 | 58 | ||
| 58 | 59 | func TestMaintainerOneRunAtATimePerRepo(t *testing.T) { | |
| 60 | + | t.Parallel() | |
| 59 | 61 | synctest.Test(t, func(t *testing.T) { | |
| 60 | 62 | ctx := context.Background() | |
| 61 | 63 | n, other := name(t, "tools"), name(t, "other") | |
| @@ -107,8 +109,10 @@ func TestMaintainerOneRunAtATimePerRepo(t *testing.T) { | |||
| 107 | 109 | } | |
| 108 | 110 | ||
| 109 | 111 | func TestMaintainerTellsTheReaderAfterARun(t *testing.T) { | |
| 112 | + | t.Parallel() | |
| 110 | 113 | for label, failure := range map[string]error{"success": nil, "failure": errors.New("boom")} { | |
| 111 | 114 | t.Run(label, func(t *testing.T) { | |
| 115 | + | t.Parallel() | |
| 112 | 116 | synctest.Test(t, func(t *testing.T) { | |
| 113 | 117 | ctx := context.Background() | |
| 114 | 118 | n := name(t, "tools") | |
| @@ -132,6 +136,7 @@ func TestMaintainerTellsTheReaderAfterARun(t *testing.T) { | |||
| 132 | 136 | } | |
| 133 | 137 | ||
| 134 | 138 | func TestMaintainerFailureIsLoggedNotFatal(t *testing.T) { | |
| 139 | + | t.Parallel() | |
| 135 | 140 | synctest.Test(t, func(t *testing.T) { | |
| 136 | 141 | ctx := context.Background() | |
| 137 | 142 | n := name(t, "tools") | |
| @@ -153,10 +158,12 @@ func TestMaintainerFailureIsLoggedNotFatal(t *testing.T) { | |||
| 153 | 158 | } | |
| 154 | 159 | ||
| 155 | 160 | func TestMaintainerClose(t *testing.T) { | |
| 161 | + | t.Parallel() | |
| 156 | 162 | ctx := context.Background() | |
| 157 | 163 | n := name(t, "tools") | |
| 158 | 164 | ||
| 159 | 165 | t.Run("pending runs are dropped", func(t *testing.T) { | |
| 166 | + | t.Parallel() | |
| 160 | 167 | synctest.Test(t, func(t *testing.T) { | |
| 161 | 168 | git := mocks.NewGitRepos(t) // no Maintain expected | |
| 162 | 169 | m := app.NewMaintainer(ctx, git, app.MaintenanceDelay) | |
| @@ -169,6 +176,7 @@ func TestMaintainerClose(t *testing.T) { | |||
| 169 | 176 | }) | |
| 170 | 177 | ||
| 171 | 178 | t.Run("a run in progress is cancelled and awaited", func(t *testing.T) { | |
| 179 | + | t.Parallel() | |
| 172 | 180 | synctest.Test(t, func(t *testing.T) { | |
| 173 | 181 | git := mocks.NewGitRepos(t) | |
| 174 | 182 | var cancelled atomic.Bool | |
| @@ -188,6 +196,7 @@ func TestMaintainerClose(t *testing.T) { | |||
| 188 | 196 | } | |
| 189 | 197 | ||
| 190 | 198 | func TestMaintainerConcurrentSchedule(t *testing.T) { | |
| 199 | + | t.Parallel() | |
| 191 | 200 | synctest.Test(t, func(t *testing.T) { | |
| 192 | 201 | ctx := context.Background() | |
| 193 | 202 | git := mocks.NewGitRepos(t) | |
| @@ -25,6 +25,7 @@ import ( | |||||
| 25 | // hangs is reported as a bubble deadlock instead of a timeout. | 25 | // hangs is reported as a bubble deadlock instead of a timeout. | ||
| 26 | 26 | ||||
| 27 | func TestMaintainerDebounces(t *testing.T) { | 27 | func TestMaintainerDebounces(t *testing.T) { | ||
| 28 | + | t.Parallel() | |||
| 28 | synctest.Test(t, func(t *testing.T) { | 29 | synctest.Test(t, func(t *testing.T) { | ||
| 29 | ctx := context.Background() | 30 | ctx := context.Background() | ||
| 30 | n := name(t, "tools") | 31 | n := name(t, "tools") | ||
| @@ -56,6 +57,7 @@ func TestMaintainerDebounces(t *testing.T) { | |||||
| 56 | } | 57 | } | ||
| 57 | 58 | ||||
| 58 | func TestMaintainerOneRunAtATimePerRepo(t *testing.T) { | 59 | func TestMaintainerOneRunAtATimePerRepo(t *testing.T) { | ||
| 60 | + | t.Parallel() | |||
| 59 | synctest.Test(t, func(t *testing.T) { | 61 | synctest.Test(t, func(t *testing.T) { | ||
| 60 | ctx := context.Background() | 62 | ctx := context.Background() | ||
| 61 | n, other := name(t, "tools"), name(t, "other") | 63 | n, other := name(t, "tools"), name(t, "other") | ||
| @@ -107,8 +109,10 @@ func TestMaintainerOneRunAtATimePerRepo(t *testing.T) { | |||||
| 107 | } | 109 | } | ||
| 108 | 110 | ||||
| 109 | func TestMaintainerTellsTheReaderAfterARun(t *testing.T) { | 111 | func TestMaintainerTellsTheReaderAfterARun(t *testing.T) { | ||
| 112 | + | t.Parallel() | |||
| 110 | for label, failure := range map[string]error{"success": nil, "failure": errors.New("boom")} { | 113 | for label, failure := range map[string]error{"success": nil, "failure": errors.New("boom")} { | ||
| 111 | t.Run(label, func(t *testing.T) { | 114 | t.Run(label, func(t *testing.T) { | ||
| 115 | + | t.Parallel() | |||
| 112 | synctest.Test(t, func(t *testing.T) { | 116 | synctest.Test(t, func(t *testing.T) { | ||
| 113 | ctx := context.Background() | 117 | ctx := context.Background() | ||
| 114 | n := name(t, "tools") | 118 | n := name(t, "tools") | ||
| @@ -132,6 +136,7 @@ func TestMaintainerTellsTheReaderAfterARun(t *testing.T) { | |||||
| 132 | } | 136 | } | ||
| 133 | 137 | ||||
| 134 | func TestMaintainerFailureIsLoggedNotFatal(t *testing.T) { | 138 | func TestMaintainerFailureIsLoggedNotFatal(t *testing.T) { | ||
| 139 | + | t.Parallel() | |||
| 135 | synctest.Test(t, func(t *testing.T) { | 140 | synctest.Test(t, func(t *testing.T) { | ||
| 136 | ctx := context.Background() | 141 | ctx := context.Background() | ||
| 137 | n := name(t, "tools") | 142 | n := name(t, "tools") | ||
| @@ -153,10 +158,12 @@ func TestMaintainerFailureIsLoggedNotFatal(t *testing.T) { | |||||
| 153 | } | 158 | } | ||
| 154 | 159 | ||||
| 155 | func TestMaintainerClose(t *testing.T) { | 160 | func TestMaintainerClose(t *testing.T) { | ||
| 161 | + | t.Parallel() | |||
| 156 | ctx := context.Background() | 162 | ctx := context.Background() | ||
| 157 | n := name(t, "tools") | 163 | n := name(t, "tools") | ||
| 158 | 164 | ||||
| 159 | t.Run("pending runs are dropped", func(t *testing.T) { | 165 | t.Run("pending runs are dropped", func(t *testing.T) { | ||
| 166 | + | t.Parallel() | |||
| 160 | synctest.Test(t, func(t *testing.T) { | 167 | synctest.Test(t, func(t *testing.T) { | ||
| 161 | git := mocks.NewGitRepos(t) // no Maintain expected | 168 | git := mocks.NewGitRepos(t) // no Maintain expected | ||
| 162 | m := app.NewMaintainer(ctx, git, app.MaintenanceDelay) | 169 | m := app.NewMaintainer(ctx, git, app.MaintenanceDelay) | ||
| @@ -169,6 +176,7 @@ func TestMaintainerClose(t *testing.T) { | |||||
| 169 | }) | 176 | }) | ||
| 170 | 177 | ||||
| 171 | t.Run("a run in progress is cancelled and awaited", func(t *testing.T) { | 178 | t.Run("a run in progress is cancelled and awaited", func(t *testing.T) { | ||
| 179 | + | t.Parallel() | |||
| 172 | synctest.Test(t, func(t *testing.T) { | 180 | synctest.Test(t, func(t *testing.T) { | ||
| 173 | git := mocks.NewGitRepos(t) | 181 | git := mocks.NewGitRepos(t) | ||
| 174 | var cancelled atomic.Bool | 182 | var cancelled atomic.Bool | ||
| @@ -188,6 +196,7 @@ func TestMaintainerClose(t *testing.T) { | |||||
| 188 | } | 196 | } | ||
| 189 | 197 | ||||
| 190 | func TestMaintainerConcurrentSchedule(t *testing.T) { | 198 | func TestMaintainerConcurrentSchedule(t *testing.T) { | ||
| 199 | + | t.Parallel() | |||
| 191 | synctest.Test(t, func(t *testing.T) { | 200 | synctest.Test(t, func(t *testing.T) { | ||
| 192 | ctx := context.Background() | 201 | ctx := context.Background() | ||
| 193 | git := mocks.NewGitRepos(t) | 202 | git := mocks.NewGitRepos(t) | ||
M internal/app/mergerequests_delete_test.go +10 -0
| @@ -16,8 +16,10 @@ import ( | |||
| 16 | 16 | ||
| 17 | 17 | // A merge can delete its source branch; the push event carries both refs. | |
| 18 | 18 | func TestMergeDeletesTheSourceBranch(t *testing.T) { | |
| 19 | + | t.Parallel() | |
| 19 | 20 | for _, mode := range []domain.MergeMode{domain.MergeModeFastForward, domain.MergeModeSquash} { | |
| 20 | 21 | t.Run(string(mode), func(t *testing.T) { | |
| 22 | + | t.Parallel() | |
| 21 | 23 | r := newMRRig(t) | |
| 22 | 24 | main := r.seed() | |
| 23 | 25 | head := r.feature("feat") | |
| @@ -47,6 +49,7 @@ func TestMergeDeletesTheSourceBranch(t *testing.T) { | |||
| 47 | 49 | } | |
| 48 | 50 | ||
| 49 | 51 | t.Run("without the option the branch stays", func(t *testing.T) { | |
| 52 | + | t.Parallel() | |
| 50 | 53 | r := newMRRig(t) | |
| 51 | 54 | r.seed() | |
| 52 | 55 | head := r.feature("feat") | |
| @@ -61,7 +64,9 @@ func TestMergeDeletesTheSourceBranch(t *testing.T) { | |||
| 61 | 64 | // Deleting the source is refused before the merge when the branch is still | |
| 62 | 65 | // needed: nothing changes. | |
| 63 | 66 | func TestMergeRefusesToDeleteANeededBranch(t *testing.T) { | |
| 67 | + | t.Parallel() | |
| 64 | 68 | t.Run("another open request uses it as its source", func(t *testing.T) { | |
| 69 | + | t.Parallel() | |
| 65 | 70 | r := newMRRig(t) | |
| 66 | 71 | main := r.seed() | |
| 67 | 72 | head := r.feature("feat") | |
| @@ -82,6 +87,7 @@ func TestMergeRefusesToDeleteANeededBranch(t *testing.T) { | |||
| 82 | 87 | }) | |
| 83 | 88 | ||
| 84 | 89 | t.Run("another open request targets it", func(t *testing.T) { | |
| 90 | + | t.Parallel() | |
| 85 | 91 | r := newMRRig(t) | |
| 86 | 92 | r.seed() | |
| 87 | 93 | head := r.feature("feat") | |
| @@ -97,6 +103,7 @@ func TestMergeRefusesToDeleteANeededBranch(t *testing.T) { | |||
| 97 | 103 | }) | |
| 98 | 104 | ||
| 99 | 105 | t.Run("the default branch", func(t *testing.T) { | |
| 106 | + | t.Parallel() | |
| 100 | 107 | r := newMRRig(t) | |
| 101 | 108 | main := r.seed() | |
| 102 | 109 | r.sh("checkout", "-q", "-B", "old", "main~1") | |
| @@ -115,6 +122,7 @@ func TestMergeRefusesToDeleteANeededBranch(t *testing.T) { | |||
| 115 | 122 | // After a squash with deletion, gc may prune the reviewed commits: the merged | |
| 116 | 123 | // request still reads, without commits or diff. | |
| 117 | 124 | func TestSquashMergedRequestReadsAfterItsCommitsArePruned(t *testing.T) { | |
| 125 | + | t.Parallel() | |
| 118 | 126 | r := newMRRig(t) | |
| 119 | 127 | r.seed() | |
| 120 | 128 | head := r.feature("feat") | |
| @@ -141,6 +149,7 @@ func TestSquashMergedRequestReadsAfterItsCommitsArePruned(t *testing.T) { | |||
| 141 | 149 | // A request opened on a branch a merge is deleting waits for the merge lock | |
| 142 | 150 | // and then sees the branch is gone. | |
| 143 | 151 | func TestCreateAfterAMergeDeletedTheBranch(t *testing.T) { | |
| 152 | + | t.Parallel() | |
| 144 | 153 | r := newMRRig(t) | |
| 145 | 154 | r.seed() | |
| 146 | 155 | head := r.feature("feat") | |
| @@ -165,6 +174,7 @@ func (g movingGit) DeleteBranch(ctx context.Context, repo domain.RepoName, branc | |||
| 165 | 174 | ||
| 166 | 175 | // A branch that moves after the merge is kept: the merge stands. | |
| 167 | 176 | func TestMergeKeepsASourceThatMovedAfterTheMerge(t *testing.T) { | |
| 177 | + | t.Parallel() | |
| 168 | 178 | r := newMRRig(t) | |
| 169 | 179 | r.seed() | |
| 170 | 180 | head := r.feature("feat") | |
| @@ -16,8 +16,10 @@ import ( | |||||
| 16 | 16 | ||||
| 17 | // A merge can delete its source branch; the push event carries both refs. | 17 | // A merge can delete its source branch; the push event carries both refs. | ||
| 18 | func TestMergeDeletesTheSourceBranch(t *testing.T) { | 18 | func TestMergeDeletesTheSourceBranch(t *testing.T) { | ||
| 19 | + | t.Parallel() | |||
| 19 | for _, mode := range []domain.MergeMode{domain.MergeModeFastForward, domain.MergeModeSquash} { | 20 | for _, mode := range []domain.MergeMode{domain.MergeModeFastForward, domain.MergeModeSquash} { | ||
| 20 | t.Run(string(mode), func(t *testing.T) { | 21 | t.Run(string(mode), func(t *testing.T) { | ||
| 22 | + | t.Parallel() | |||
| 21 | r := newMRRig(t) | 23 | r := newMRRig(t) | ||
| 22 | main := r.seed() | 24 | main := r.seed() | ||
| 23 | head := r.feature("feat") | 25 | head := r.feature("feat") | ||
| @@ -47,6 +49,7 @@ func TestMergeDeletesTheSourceBranch(t *testing.T) { | |||||
| 47 | } | 49 | } | ||
| 48 | 50 | ||||
| 49 | t.Run("without the option the branch stays", func(t *testing.T) { | 51 | t.Run("without the option the branch stays", func(t *testing.T) { | ||
| 52 | + | t.Parallel() | |||
| 50 | r := newMRRig(t) | 53 | r := newMRRig(t) | ||
| 51 | r.seed() | 54 | r.seed() | ||
| 52 | head := r.feature("feat") | 55 | head := r.feature("feat") | ||
| @@ -61,7 +64,9 @@ func TestMergeDeletesTheSourceBranch(t *testing.T) { | |||||
| 61 | // Deleting the source is refused before the merge when the branch is still | 64 | // Deleting the source is refused before the merge when the branch is still | ||
| 62 | // needed: nothing changes. | 65 | // needed: nothing changes. | ||
| 63 | func TestMergeRefusesToDeleteANeededBranch(t *testing.T) { | 66 | func TestMergeRefusesToDeleteANeededBranch(t *testing.T) { | ||
| 67 | + | t.Parallel() | |||
| 64 | t.Run("another open request uses it as its source", func(t *testing.T) { | 68 | t.Run("another open request uses it as its source", func(t *testing.T) { | ||
| 69 | + | t.Parallel() | |||
| 65 | r := newMRRig(t) | 70 | r := newMRRig(t) | ||
| 66 | main := r.seed() | 71 | main := r.seed() | ||
| 67 | head := r.feature("feat") | 72 | head := r.feature("feat") | ||
| @@ -82,6 +87,7 @@ func TestMergeRefusesToDeleteANeededBranch(t *testing.T) { | |||||
| 82 | }) | 87 | }) | ||
| 83 | 88 | ||||
| 84 | t.Run("another open request targets it", func(t *testing.T) { | 89 | t.Run("another open request targets it", func(t *testing.T) { | ||
| 90 | + | t.Parallel() | |||
| 85 | r := newMRRig(t) | 91 | r := newMRRig(t) | ||
| 86 | r.seed() | 92 | r.seed() | ||
| 87 | head := r.feature("feat") | 93 | head := r.feature("feat") | ||
| @@ -97,6 +103,7 @@ func TestMergeRefusesToDeleteANeededBranch(t *testing.T) { | |||||
| 97 | }) | 103 | }) | ||
| 98 | 104 | ||||
| 99 | t.Run("the default branch", func(t *testing.T) { | 105 | t.Run("the default branch", func(t *testing.T) { | ||
| 106 | + | t.Parallel() | |||
| 100 | r := newMRRig(t) | 107 | r := newMRRig(t) | ||
| 101 | main := r.seed() | 108 | main := r.seed() | ||
| 102 | r.sh("checkout", "-q", "-B", "old", "main~1") | 109 | r.sh("checkout", "-q", "-B", "old", "main~1") | ||
| @@ -115,6 +122,7 @@ func TestMergeRefusesToDeleteANeededBranch(t *testing.T) { | |||||
| 115 | // After a squash with deletion, gc may prune the reviewed commits: the merged | 122 | // After a squash with deletion, gc may prune the reviewed commits: the merged | ||
| 116 | // request still reads, without commits or diff. | 123 | // request still reads, without commits or diff. | ||
| 117 | func TestSquashMergedRequestReadsAfterItsCommitsArePruned(t *testing.T) { | 124 | func TestSquashMergedRequestReadsAfterItsCommitsArePruned(t *testing.T) { | ||
| 125 | + | t.Parallel() | |||
| 118 | r := newMRRig(t) | 126 | r := newMRRig(t) | ||
| 119 | r.seed() | 127 | r.seed() | ||
| 120 | head := r.feature("feat") | 128 | head := r.feature("feat") | ||
| @@ -141,6 +149,7 @@ func TestSquashMergedRequestReadsAfterItsCommitsArePruned(t *testing.T) { | |||||
| 141 | // A request opened on a branch a merge is deleting waits for the merge lock | 149 | // A request opened on a branch a merge is deleting waits for the merge lock | ||
| 142 | // and then sees the branch is gone. | 150 | // and then sees the branch is gone. | ||
| 143 | func TestCreateAfterAMergeDeletedTheBranch(t *testing.T) { | 151 | func TestCreateAfterAMergeDeletedTheBranch(t *testing.T) { | ||
| 152 | + | t.Parallel() | |||
| 144 | r := newMRRig(t) | 153 | r := newMRRig(t) | ||
| 145 | r.seed() | 154 | r.seed() | ||
| 146 | head := r.feature("feat") | 155 | head := r.feature("feat") | ||
| @@ -165,6 +174,7 @@ func (g movingGit) DeleteBranch(ctx context.Context, repo domain.RepoName, branc | |||||
| 165 | 174 | ||||
| 166 | // A branch that moves after the merge is kept: the merge stands. | 175 | // A branch that moves after the merge is kept: the merge stands. | ||
| 167 | func TestMergeKeepsASourceThatMovedAfterTheMerge(t *testing.T) { | 176 | func TestMergeKeepsASourceThatMovedAfterTheMerge(t *testing.T) { | ||
| 177 | + | t.Parallel() | |||
| 168 | r := newMRRig(t) | 178 | r := newMRRig(t) | ||
| 169 | r.seed() | 179 | r.seed() | ||
| 170 | head := r.feature("feat") | 180 | head := r.feature("feat") | ||
M internal/app/mergerequests_test.go +6 -0
| @@ -49,10 +49,12 @@ func newMRUnit(t *testing.T) *mrUnit { | |||
| 49 | 49 | // GO-004: a failure to read Git is an error for the log, not a field violation | |
| 50 | 50 | // that would show its text to the caller. | |
| 51 | 51 | func TestCreateReturnsGitFailuresAsErrors(t *testing.T) { | |
| 52 | + | t.Parallel() | |
| 52 | 53 | boom := errors.New("fatal: /srv/ermite/repos/proj.git: permission denied") | |
| 53 | 54 | params := app.CreateMergeRequestParams{Title: "t", Source: "feat", Target: "main"} | |
| 54 | 55 | ||
| 55 | 56 | t.Run("reading a branch", func(t *testing.T) { | |
| 57 | + | t.Parallel() | |
| 56 | 58 | u := newMRUnit(t) | |
| 57 | 59 | u.meta.EXPECT().Get(mock.Anything, u.repo.Name).Return(u.repo, nil) | |
| 58 | 60 | u.git.EXPECT().BranchHead(mock.Anything, u.repo.Name, "feat").Return(domain.ObjectID{}, boom) | |
| @@ -64,6 +66,7 @@ func TestCreateReturnsGitFailuresAsErrors(t *testing.T) { | |||
| 64 | 66 | }) | |
| 65 | 67 | ||
| 66 | 68 | t.Run("reading the default branch", func(t *testing.T) { | |
| 69 | + | t.Parallel() | |
| 67 | 70 | u := newMRUnit(t) | |
| 68 | 71 | u.meta.EXPECT().Get(mock.Anything, u.repo.Name).Return(u.repo, nil) | |
| 69 | 72 | u.repos.EXPECT().DefaultBranch(mock.Anything, u.repo.Name).Return("", false, boom) | |
| @@ -74,6 +77,7 @@ func TestCreateReturnsGitFailuresAsErrors(t *testing.T) { | |||
| 74 | 77 | }) | |
| 75 | 78 | ||
| 76 | 79 | t.Run("a branch that does not exist stays a violation", func(t *testing.T) { | |
| 80 | + | t.Parallel() | |
| 77 | 81 | u := newMRUnit(t) | |
| 78 | 82 | u.meta.EXPECT().Get(mock.Anything, u.repo.Name).Return(u.repo, nil) | |
| 79 | 83 | u.git.EXPECT().BranchHead(mock.Anything, u.repo.Name, "feat").Return(domain.ObjectID{}, domain.ErrNotFound) | |
| @@ -91,12 +95,14 @@ func someID(t *testing.T) domain.ObjectID { | |||
| 91 | 95 | // GO-003: the settings are read again under the merge lock; a failure of that | |
| 92 | 96 | // read is returned, and a repository deleted meanwhile is not found. | |
| 93 | 97 | func TestMergeReturnsTheFailureToReadTheSettings(t *testing.T) { | |
| 98 | + | t.Parallel() | |
| 94 | 99 | boom := errors.New("kv: closed") | |
| 95 | 100 | for label, tc := range map[string]struct{ err, want error }{ | |
| 96 | 101 | "read failure": {boom, boom}, | |
| 97 | 102 | "deleted meanwhile": {domain.ErrNotFound, domain.ErrNotFound}, | |
| 98 | 103 | } { | |
| 99 | 104 | t.Run(label, func(t *testing.T) { | |
| 105 | + | t.Parallel() | |
| 100 | 106 | u := newMRUnit(t) | |
| 101 | 107 | u.meta.EXPECT().Get(mock.Anything, u.repo.Name).Return(u.repo, nil).Once() | |
| 102 | 108 | u.meta.EXPECT().Get(mock.Anything, u.repo.Name).Return(domain.Repo{}, tc.err).Once() | |
| @@ -49,10 +49,12 @@ func newMRUnit(t *testing.T) *mrUnit { | |||||
| 49 | // GO-004: a failure to read Git is an error for the log, not a field violation | 49 | // GO-004: a failure to read Git is an error for the log, not a field violation | ||
| 50 | // that would show its text to the caller. | 50 | // that would show its text to the caller. | ||
| 51 | func TestCreateReturnsGitFailuresAsErrors(t *testing.T) { | 51 | func TestCreateReturnsGitFailuresAsErrors(t *testing.T) { | ||
| 52 | + | t.Parallel() | |||
| 52 | boom := errors.New("fatal: /srv/ermite/repos/proj.git: permission denied") | 53 | boom := errors.New("fatal: /srv/ermite/repos/proj.git: permission denied") | ||
| 53 | params := app.CreateMergeRequestParams{Title: "t", Source: "feat", Target: "main"} | 54 | params := app.CreateMergeRequestParams{Title: "t", Source: "feat", Target: "main"} | ||
| 54 | 55 | ||||
| 55 | t.Run("reading a branch", func(t *testing.T) { | 56 | t.Run("reading a branch", func(t *testing.T) { | ||
| 57 | + | t.Parallel() | |||
| 56 | u := newMRUnit(t) | 58 | u := newMRUnit(t) | ||
| 57 | u.meta.EXPECT().Get(mock.Anything, u.repo.Name).Return(u.repo, nil) | 59 | u.meta.EXPECT().Get(mock.Anything, u.repo.Name).Return(u.repo, nil) | ||
| 58 | u.git.EXPECT().BranchHead(mock.Anything, u.repo.Name, "feat").Return(domain.ObjectID{}, boom) | 60 | u.git.EXPECT().BranchHead(mock.Anything, u.repo.Name, "feat").Return(domain.ObjectID{}, boom) | ||
| @@ -64,6 +66,7 @@ func TestCreateReturnsGitFailuresAsErrors(t *testing.T) { | |||||
| 64 | }) | 66 | }) | ||
| 65 | 67 | ||||
| 66 | t.Run("reading the default branch", func(t *testing.T) { | 68 | t.Run("reading the default branch", func(t *testing.T) { | ||
| 69 | + | t.Parallel() | |||
| 67 | u := newMRUnit(t) | 70 | u := newMRUnit(t) | ||
| 68 | u.meta.EXPECT().Get(mock.Anything, u.repo.Name).Return(u.repo, nil) | 71 | u.meta.EXPECT().Get(mock.Anything, u.repo.Name).Return(u.repo, nil) | ||
| 69 | u.repos.EXPECT().DefaultBranch(mock.Anything, u.repo.Name).Return("", false, boom) | 72 | u.repos.EXPECT().DefaultBranch(mock.Anything, u.repo.Name).Return("", false, boom) | ||
| @@ -74,6 +77,7 @@ func TestCreateReturnsGitFailuresAsErrors(t *testing.T) { | |||||
| 74 | }) | 77 | }) | ||
| 75 | 78 | ||||
| 76 | t.Run("a branch that does not exist stays a violation", func(t *testing.T) { | 79 | t.Run("a branch that does not exist stays a violation", func(t *testing.T) { | ||
| 80 | + | t.Parallel() | |||
| 77 | u := newMRUnit(t) | 81 | u := newMRUnit(t) | ||
| 78 | u.meta.EXPECT().Get(mock.Anything, u.repo.Name).Return(u.repo, nil) | 82 | u.meta.EXPECT().Get(mock.Anything, u.repo.Name).Return(u.repo, nil) | ||
| 79 | u.git.EXPECT().BranchHead(mock.Anything, u.repo.Name, "feat").Return(domain.ObjectID{}, domain.ErrNotFound) | 83 | u.git.EXPECT().BranchHead(mock.Anything, u.repo.Name, "feat").Return(domain.ObjectID{}, domain.ErrNotFound) | ||
| @@ -91,12 +95,14 @@ func someID(t *testing.T) domain.ObjectID { | |||||
| 91 | // GO-003: the settings are read again under the merge lock; a failure of that | 95 | // GO-003: the settings are read again under the merge lock; a failure of that | ||
| 92 | // read is returned, and a repository deleted meanwhile is not found. | 96 | // read is returned, and a repository deleted meanwhile is not found. | ||
| 93 | func TestMergeReturnsTheFailureToReadTheSettings(t *testing.T) { | 97 | func TestMergeReturnsTheFailureToReadTheSettings(t *testing.T) { | ||
| 98 | + | t.Parallel() | |||
| 94 | boom := errors.New("kv: closed") | 99 | boom := errors.New("kv: closed") | ||
| 95 | for label, tc := range map[string]struct{ err, want error }{ | 100 | for label, tc := range map[string]struct{ err, want error }{ | ||
| 96 | "read failure": {boom, boom}, | 101 | "read failure": {boom, boom}, | ||
| 97 | "deleted meanwhile": {domain.ErrNotFound, domain.ErrNotFound}, | 102 | "deleted meanwhile": {domain.ErrNotFound, domain.ErrNotFound}, | ||
| 98 | } { | 103 | } { | ||
| 99 | t.Run(label, func(t *testing.T) { | 104 | t.Run(label, func(t *testing.T) { | ||
| 105 | + | t.Parallel() | |||
| 100 | u := newMRUnit(t) | 106 | u := newMRUnit(t) | ||
| 101 | u.meta.EXPECT().Get(mock.Anything, u.repo.Name).Return(u.repo, nil).Once() | 107 | u.meta.EXPECT().Get(mock.Anything, u.repo.Name).Return(u.repo, nil).Once() | ||
| 102 | u.meta.EXPECT().Get(mock.Anything, u.repo.Name).Return(domain.Repo{}, tc.err).Once() | 108 | u.meta.EXPECT().Get(mock.Anything, u.repo.Name).Return(domain.Repo{}, tc.err).Once() | ||
M internal/app/pushes_test.go +25 -0
| @@ -52,6 +52,7 @@ const ( | |||
| 52 | 52 | ) | |
| 53 | 53 | ||
| 54 | 54 | func TestPushesOpen(t *testing.T) { | |
| 55 | + | t.Parallel() | |
| 55 | 56 | ctx := context.Background() | |
| 56 | 57 | n := name(t, "tools") | |
| 57 | 58 | priv := domain.Repo{Name: n, Visibility: domain.VisibilityPrivate} | |
| @@ -79,6 +80,7 @@ func TestPushesOpen(t *testing.T) { | |||
| 79 | 80 | } | |
| 80 | 81 | for _, tt := range tests { | |
| 81 | 82 | t.Run(tt.name, func(t *testing.T) { | |
| 83 | + | t.Parallel() | |
| 82 | 84 | f := newPushFixture(t) | |
| 83 | 85 | if !tt.p.IsAnonymous() { | |
| 84 | 86 | if tt.repo != nil { | |
| @@ -98,6 +100,7 @@ func TestPushesOpen(t *testing.T) { | |||
| 98 | 100 | } | |
| 99 | 101 | ||
| 100 | 102 | t.Run("creates a private repository and publishes RepoCreated", func(t *testing.T) { | |
| 103 | + | t.Parallel() | |
| 101 | 104 | f := newPushFixture(t) | |
| 102 | 105 | f.meta.EXPECT().Get(ctx, n).Return(domain.Repo{}, domain.ErrNotFound) | |
| 103 | 106 | f.git.EXPECT().Stage(ctx).Return(ports.StagedID("s1"), nil) | |
| @@ -110,6 +113,7 @@ func TestPushesOpen(t *testing.T) { | |||
| 110 | 113 | }) | |
| 111 | 114 | ||
| 112 | 115 | t.Run("a concurrent first push wins: this one pushes to its repository", func(t *testing.T) { | |
| 116 | + | t.Parallel() | |
| 113 | 117 | f := newPushFixture(t) | |
| 114 | 118 | f.meta.EXPECT().Get(ctx, n).Return(domain.Repo{}, domain.ErrNotFound).Once() | |
| 115 | 119 | f.git.EXPECT().Stage(ctx).Return(ports.StagedID("s1"), nil) | |
| @@ -122,6 +126,7 @@ func TestPushesOpen(t *testing.T) { | |||
| 122 | 126 | }) | |
| 123 | 127 | ||
| 124 | 128 | t.Run("store failure", func(t *testing.T) { | |
| 129 | + | t.Parallel() | |
| 125 | 130 | f := newPushFixture(t) | |
| 126 | 131 | boom := errors.New("boom") | |
| 127 | 132 | f.meta.EXPECT().Get(ctx, n).Return(domain.Repo{}, boom) | |
| @@ -130,6 +135,7 @@ func TestPushesOpen(t *testing.T) { | |||
| 130 | 135 | }) | |
| 131 | 136 | ||
| 132 | 137 | t.Run("create failure is returned and nothing is published", func(t *testing.T) { | |
| 138 | + | t.Parallel() | |
| 133 | 139 | f := newPushFixture(t) | |
| 134 | 140 | boom := errors.New("boom") | |
| 135 | 141 | f.meta.EXPECT().Get(ctx, n).Return(domain.Repo{}, domain.ErrNotFound) | |
| @@ -140,6 +146,7 @@ func TestPushesOpen(t *testing.T) { | |||
| 140 | 146 | } | |
| 141 | 147 | ||
| 142 | 148 | func TestPushesCheck(t *testing.T) { | |
| 149 | + | t.Parallel() | |
| 143 | 150 | ctx := context.Background() | |
| 144 | 151 | n := name(t, "tools") | |
| 145 | 152 | ups := []domain.RefUpdate{{Old: domain.ZeroOID, New: oidA, Ref: "refs/heads/main"}} | |
| @@ -147,29 +154,34 @@ func TestPushesCheck(t *testing.T) { | |||
| 147 | 154 | reader := domain.TokenPrincipal(domain.Token{ID: "r1", Scopes: scopes(domain.ScopeRepoRead)}) | |
| 148 | 155 | ||
| 149 | 156 | t.Run("root is always valid and needs no lookup", func(t *testing.T) { | |
| 157 | + | t.Parallel() | |
| 150 | 158 | f := newPushFixture(t) // the token store mock expects no call | |
| 151 | 159 | require.NoError(t, f.pushes.Check(ctx, domain.Root(), n, ups)) | |
| 152 | 160 | }) | |
| 153 | 161 | ||
| 154 | 162 | t.Run("a writer whose token still exists", func(t *testing.T) { | |
| 163 | + | t.Parallel() | |
| 155 | 164 | f := newPushFixture(t) | |
| 156 | 165 | f.tokens.EXPECT().Get(ctx, "w1").Return(domain.Token{ID: "w1", Scopes: scopes(domain.ScopeRepoWrite)}, nil) | |
| 157 | 166 | require.NoError(t, f.pushes.Check(ctx, writer, n, ups)) | |
| 158 | 167 | }) | |
| 159 | 168 | ||
| 160 | 169 | t.Run("a token revoked during the push is rejected", func(t *testing.T) { | |
| 170 | + | t.Parallel() | |
| 161 | 171 | f := newPushFixture(t) | |
| 162 | 172 | f.tokens.EXPECT().Get(ctx, "w1").Return(domain.Token{}, domain.ErrNotFound) | |
| 163 | 173 | assert.ErrorIs(t, f.pushes.Check(ctx, writer, n, ups), domain.ErrRejected) | |
| 164 | 174 | }) | |
| 165 | 175 | ||
| 166 | 176 | t.Run("a token that expired during the push is rejected", func(t *testing.T) { | |
| 177 | + | t.Parallel() | |
| 167 | 178 | f := newPushFixture(t) | |
| 168 | 179 | f.tokens.EXPECT().Get(ctx, "w1").Return(domain.Token{ID: "w1", Scopes: scopes(domain.ScopeRepoWrite), ExpiresAt: now.Add(-time.Second)}, nil) | |
| 169 | 180 | assert.ErrorIs(t, f.pushes.Check(ctx, writer, n, ups), domain.ErrRejected) | |
| 170 | 181 | }) | |
| 171 | 182 | ||
| 172 | 183 | t.Run("a store failure fails closed", func(t *testing.T) { | |
| 184 | + | t.Parallel() | |
| 173 | 185 | f := newPushFixture(t) | |
| 174 | 186 | boom := errors.New("boom") | |
| 175 | 187 | f.tokens.EXPECT().Get(ctx, "w1").Return(domain.Token{}, boom) | |
| @@ -179,18 +191,21 @@ func TestPushesCheck(t *testing.T) { | |||
| 179 | 191 | }) | |
| 180 | 192 | ||
| 181 | 193 | t.Run("a token without write access is rejected", func(t *testing.T) { | |
| 194 | + | t.Parallel() | |
| 182 | 195 | f := newPushFixture(t) | |
| 183 | 196 | f.tokens.EXPECT().Get(ctx, "r1").Return(domain.Token{ID: "r1", Scopes: scopes(domain.ScopeRepoRead)}, nil) | |
| 184 | 197 | assert.ErrorIs(t, f.pushes.Check(ctx, reader, n, ups), domain.ErrRejected) | |
| 185 | 198 | }) | |
| 186 | 199 | ||
| 187 | 200 | t.Run("anonymous is rejected", func(t *testing.T) { | |
| 201 | + | t.Parallel() | |
| 188 | 202 | f := newPushFixture(t) | |
| 189 | 203 | assert.ErrorIs(t, f.pushes.Check(ctx, domain.Anonymous(), n, ups), domain.ErrRejected) | |
| 190 | 204 | }) | |
| 191 | 205 | } | |
| 192 | 206 | ||
| 193 | 207 | func TestPushesRecord(t *testing.T) { | |
| 208 | + | t.Parallel() | |
| 194 | 209 | ctx := context.Background() | |
| 195 | 210 | n := name(t, "tools") | |
| 196 | 211 | p := tokenPrincipal(domain.ScopeRepoWrite) | |
| @@ -201,6 +216,7 @@ func TestPushesRecord(t *testing.T) { | |||
| 201 | 216 | } | |
| 202 | 217 | ||
| 203 | 218 | t.Run("sets pushed_at, publishes the exact updates, schedules maintenance", func(t *testing.T) { | |
| 219 | + | t.Parallel() | |
| 204 | 220 | f := newPushFixture(t) | |
| 205 | 221 | f.sched.EXPECT().Schedule(ctx, n).Return().Once() | |
| 206 | 222 | repo := domain.Repo{Name: n, Visibility: domain.VisibilityPrivate, CreatedAt: now.Add(-time.Hour)} | |
| @@ -213,6 +229,7 @@ func TestPushesRecord(t *testing.T) { | |||
| 213 | 229 | }) | |
| 214 | 230 | ||
| 215 | 231 | t.Run("tells the reader that the repository changed", func(t *testing.T) { | |
| 232 | + | t.Parallel() | |
| 216 | 233 | f := newPushFixture(t) | |
| 217 | 234 | f.sched.EXPECT().Schedule(ctx, n).Return().Once() | |
| 218 | 235 | fresh := mocks.NewRepoFreshener(t) | |
| @@ -226,6 +243,7 @@ func TestPushesRecord(t *testing.T) { | |||
| 226 | 243 | }) | |
| 227 | 244 | ||
| 228 | 245 | t.Run("pushed_at retries after a conflict and never goes backwards", func(t *testing.T) { | |
| 246 | + | t.Parallel() | |
| 229 | 247 | f := newPushFixture(t) | |
| 230 | 248 | f.sched.EXPECT().Schedule(ctx, n).Return().Once() | |
| 231 | 249 | repo := domain.Repo{Name: n} | |
| @@ -239,6 +257,7 @@ func TestPushesRecord(t *testing.T) { | |||
| 239 | 257 | }) | |
| 240 | 258 | ||
| 241 | 259 | t.Run("a deleted repository is not an error", func(t *testing.T) { | |
| 260 | + | t.Parallel() | |
| 242 | 261 | f := newPushFixture(t) | |
| 243 | 262 | f.sched.EXPECT().Schedule(ctx, n).Return().Once() | |
| 244 | 263 | f.meta.EXPECT().Get(ctx, n).Return(domain.Repo{}, domain.ErrNotFound) | |
| @@ -248,6 +267,7 @@ func TestPushesRecord(t *testing.T) { | |||
| 248 | 267 | }) | |
| 249 | 268 | ||
| 250 | 269 | t.Run("a repository deleted before the default branch step is not an error", func(t *testing.T) { | |
| 270 | + | t.Parallel() | |
| 251 | 271 | f := newPushFixture(t) | |
| 252 | 272 | f.sched.EXPECT().Schedule(ctx, n).Return().Once() | |
| 253 | 273 | f.meta.EXPECT().Get(ctx, n).Return(domain.Repo{}, domain.ErrNotFound) | |
| @@ -257,6 +277,7 @@ func TestPushesRecord(t *testing.T) { | |||
| 257 | 277 | }) | |
| 258 | 278 | ||
| 259 | 279 | t.Run("a repository deleted before HEAD is set is not an error", func(t *testing.T) { | |
| 280 | + | t.Parallel() | |
| 260 | 281 | f := newPushFixture(t) | |
| 261 | 282 | f.sched.EXPECT().Schedule(ctx, n).Return().Once() | |
| 262 | 283 | f.meta.EXPECT().Get(ctx, n).Return(domain.Repo{}, domain.ErrNotFound) | |
| @@ -267,6 +288,7 @@ func TestPushesRecord(t *testing.T) { | |||
| 267 | 288 | }) | |
| 268 | 289 | ||
| 269 | 290 | t.Run("every step runs when one fails", func(t *testing.T) { | |
| 291 | + | t.Parallel() | |
| 270 | 292 | f := newPushFixture(t) | |
| 271 | 293 | f.sched.EXPECT().Schedule(ctx, n).Return().Once() | |
| 272 | 294 | boom := errors.New("boom") | |
| @@ -279,6 +301,7 @@ func TestPushesRecord(t *testing.T) { | |||
| 279 | 301 | } | |
| 280 | 302 | ||
| 281 | 303 | func TestPushesDefaultBranchRule(t *testing.T) { | |
| 304 | + | t.Parallel() | |
| 282 | 305 | ctx := context.Background() | |
| 283 | 306 | n := name(t, "tools") | |
| 284 | 307 | p := domain.Root() | |
| @@ -303,6 +326,7 @@ func TestPushesDefaultBranchRule(t *testing.T) { | |||
| 303 | 326 | } | |
| 304 | 327 | for _, tt := range tests { | |
| 305 | 328 | t.Run(tt.name, func(t *testing.T) { | |
| 329 | + | t.Parallel() | |
| 306 | 330 | f := newPushFixture(t) | |
| 307 | 331 | f.sched.EXPECT().Schedule(ctx, n).Return().Once() | |
| 308 | 332 | f.meta.EXPECT().Get(ctx, n).Return(domain.Repo{Name: n, PushedAt: now}, nil) | |
| @@ -316,6 +340,7 @@ func TestPushesDefaultBranchRule(t *testing.T) { | |||
| 316 | 340 | } | |
| 317 | 341 | ||
| 318 | 342 | t.Run("set failure is reported", func(t *testing.T) { | |
| 343 | + | t.Parallel() | |
| 319 | 344 | f := newPushFixture(t) | |
| 320 | 345 | f.sched.EXPECT().Schedule(ctx, n).Return().Once() | |
| 321 | 346 | boom := errors.New("boom") | |
| @@ -52,6 +52,7 @@ const ( | |||||
| 52 | ) | 52 | ) | ||
| 53 | 53 | ||||
| 54 | func TestPushesOpen(t *testing.T) { | 54 | func TestPushesOpen(t *testing.T) { | ||
| 55 | + | t.Parallel() | |||
| 55 | ctx := context.Background() | 56 | ctx := context.Background() | ||
| 56 | n := name(t, "tools") | 57 | n := name(t, "tools") | ||
| 57 | priv := domain.Repo{Name: n, Visibility: domain.VisibilityPrivate} | 58 | priv := domain.Repo{Name: n, Visibility: domain.VisibilityPrivate} | ||
| @@ -79,6 +80,7 @@ func TestPushesOpen(t *testing.T) { | |||||
| 79 | } | 80 | } | ||
| 80 | for _, tt := range tests { | 81 | for _, tt := range tests { | ||
| 81 | t.Run(tt.name, func(t *testing.T) { | 82 | t.Run(tt.name, func(t *testing.T) { | ||
| 83 | + | t.Parallel() | |||
| 82 | f := newPushFixture(t) | 84 | f := newPushFixture(t) | ||
| 83 | if !tt.p.IsAnonymous() { | 85 | if !tt.p.IsAnonymous() { | ||
| 84 | if tt.repo != nil { | 86 | if tt.repo != nil { | ||
| @@ -98,6 +100,7 @@ func TestPushesOpen(t *testing.T) { | |||||
| 98 | } | 100 | } | ||
| 99 | 101 | ||||
| 100 | t.Run("creates a private repository and publishes RepoCreated", func(t *testing.T) { | 102 | t.Run("creates a private repository and publishes RepoCreated", func(t *testing.T) { | ||
| 103 | + | t.Parallel() | |||
| 101 | f := newPushFixture(t) | 104 | f := newPushFixture(t) | ||
| 102 | f.meta.EXPECT().Get(ctx, n).Return(domain.Repo{}, domain.ErrNotFound) | 105 | f.meta.EXPECT().Get(ctx, n).Return(domain.Repo{}, domain.ErrNotFound) | ||
| 103 | f.git.EXPECT().Stage(ctx).Return(ports.StagedID("s1"), nil) | 106 | f.git.EXPECT().Stage(ctx).Return(ports.StagedID("s1"), nil) | ||
| @@ -110,6 +113,7 @@ func TestPushesOpen(t *testing.T) { | |||||
| 110 | }) | 113 | }) | ||
| 111 | 114 | ||||
| 112 | t.Run("a concurrent first push wins: this one pushes to its repository", func(t *testing.T) { | 115 | t.Run("a concurrent first push wins: this one pushes to its repository", func(t *testing.T) { | ||
| 116 | + | t.Parallel() | |||
| 113 | f := newPushFixture(t) | 117 | f := newPushFixture(t) | ||
| 114 | f.meta.EXPECT().Get(ctx, n).Return(domain.Repo{}, domain.ErrNotFound).Once() | 118 | f.meta.EXPECT().Get(ctx, n).Return(domain.Repo{}, domain.ErrNotFound).Once() | ||
| 115 | f.git.EXPECT().Stage(ctx).Return(ports.StagedID("s1"), nil) | 119 | f.git.EXPECT().Stage(ctx).Return(ports.StagedID("s1"), nil) | ||
| @@ -122,6 +126,7 @@ func TestPushesOpen(t *testing.T) { | |||||
| 122 | }) | 126 | }) | ||
| 123 | 127 | ||||
| 124 | t.Run("store failure", func(t *testing.T) { | 128 | t.Run("store failure", func(t *testing.T) { | ||
| 129 | + | t.Parallel() | |||
| 125 | f := newPushFixture(t) | 130 | f := newPushFixture(t) | ||
| 126 | boom := errors.New("boom") | 131 | boom := errors.New("boom") | ||
| 127 | f.meta.EXPECT().Get(ctx, n).Return(domain.Repo{}, boom) | 132 | f.meta.EXPECT().Get(ctx, n).Return(domain.Repo{}, boom) | ||
| @@ -130,6 +135,7 @@ func TestPushesOpen(t *testing.T) { | |||||
| 130 | }) | 135 | }) | ||
| 131 | 136 | ||||
| 132 | t.Run("create failure is returned and nothing is published", func(t *testing.T) { | 137 | t.Run("create failure is returned and nothing is published", func(t *testing.T) { | ||
| 138 | + | t.Parallel() | |||
| 133 | f := newPushFixture(t) | 139 | f := newPushFixture(t) | ||
| 134 | boom := errors.New("boom") | 140 | boom := errors.New("boom") | ||
| 135 | f.meta.EXPECT().Get(ctx, n).Return(domain.Repo{}, domain.ErrNotFound) | 141 | f.meta.EXPECT().Get(ctx, n).Return(domain.Repo{}, domain.ErrNotFound) | ||
| @@ -140,6 +146,7 @@ func TestPushesOpen(t *testing.T) { | |||||
| 140 | } | 146 | } | ||
| 141 | 147 | ||||
| 142 | func TestPushesCheck(t *testing.T) { | 148 | func TestPushesCheck(t *testing.T) { | ||
| 149 | + | t.Parallel() | |||
| 143 | ctx := context.Background() | 150 | ctx := context.Background() | ||
| 144 | n := name(t, "tools") | 151 | n := name(t, "tools") | ||
| 145 | ups := []domain.RefUpdate{{Old: domain.ZeroOID, New: oidA, Ref: "refs/heads/main"}} | 152 | ups := []domain.RefUpdate{{Old: domain.ZeroOID, New: oidA, Ref: "refs/heads/main"}} | ||
| @@ -147,29 +154,34 @@ func TestPushesCheck(t *testing.T) { | |||||
| 147 | reader := domain.TokenPrincipal(domain.Token{ID: "r1", Scopes: scopes(domain.ScopeRepoRead)}) | 154 | reader := domain.TokenPrincipal(domain.Token{ID: "r1", Scopes: scopes(domain.ScopeRepoRead)}) | ||
| 148 | 155 | ||||
| 149 | t.Run("root is always valid and needs no lookup", func(t *testing.T) { | 156 | t.Run("root is always valid and needs no lookup", func(t *testing.T) { | ||
| 157 | + | t.Parallel() | |||
| 150 | f := newPushFixture(t) // the token store mock expects no call | 158 | f := newPushFixture(t) // the token store mock expects no call | ||
| 151 | require.NoError(t, f.pushes.Check(ctx, domain.Root(), n, ups)) | 159 | require.NoError(t, f.pushes.Check(ctx, domain.Root(), n, ups)) | ||
| 152 | }) | 160 | }) | ||
| 153 | 161 | ||||
| 154 | t.Run("a writer whose token still exists", func(t *testing.T) { | 162 | t.Run("a writer whose token still exists", func(t *testing.T) { | ||
| 163 | + | t.Parallel() | |||
| 155 | f := newPushFixture(t) | 164 | f := newPushFixture(t) | ||
| 156 | f.tokens.EXPECT().Get(ctx, "w1").Return(domain.Token{ID: "w1", Scopes: scopes(domain.ScopeRepoWrite)}, nil) | 165 | f.tokens.EXPECT().Get(ctx, "w1").Return(domain.Token{ID: "w1", Scopes: scopes(domain.ScopeRepoWrite)}, nil) | ||
| 157 | require.NoError(t, f.pushes.Check(ctx, writer, n, ups)) | 166 | require.NoError(t, f.pushes.Check(ctx, writer, n, ups)) | ||
| 158 | }) | 167 | }) | ||
| 159 | 168 | ||||
| 160 | t.Run("a token revoked during the push is rejected", func(t *testing.T) { | 169 | t.Run("a token revoked during the push is rejected", func(t *testing.T) { | ||
| 170 | + | t.Parallel() | |||
| 161 | f := newPushFixture(t) | 171 | f := newPushFixture(t) | ||
| 162 | f.tokens.EXPECT().Get(ctx, "w1").Return(domain.Token{}, domain.ErrNotFound) | 172 | f.tokens.EXPECT().Get(ctx, "w1").Return(domain.Token{}, domain.ErrNotFound) | ||
| 163 | assert.ErrorIs(t, f.pushes.Check(ctx, writer, n, ups), domain.ErrRejected) | 173 | assert.ErrorIs(t, f.pushes.Check(ctx, writer, n, ups), domain.ErrRejected) | ||
| 164 | }) | 174 | }) | ||
| 165 | 175 | ||||
| 166 | t.Run("a token that expired during the push is rejected", func(t *testing.T) { | 176 | t.Run("a token that expired during the push is rejected", func(t *testing.T) { | ||
| 177 | + | t.Parallel() | |||
| 167 | f := newPushFixture(t) | 178 | f := newPushFixture(t) | ||
| 168 | f.tokens.EXPECT().Get(ctx, "w1").Return(domain.Token{ID: "w1", Scopes: scopes(domain.ScopeRepoWrite), ExpiresAt: now.Add(-time.Second)}, nil) | 179 | f.tokens.EXPECT().Get(ctx, "w1").Return(domain.Token{ID: "w1", Scopes: scopes(domain.ScopeRepoWrite), ExpiresAt: now.Add(-time.Second)}, nil) | ||
| 169 | assert.ErrorIs(t, f.pushes.Check(ctx, writer, n, ups), domain.ErrRejected) | 180 | assert.ErrorIs(t, f.pushes.Check(ctx, writer, n, ups), domain.ErrRejected) | ||
| 170 | }) | 181 | }) | ||
| 171 | 182 | ||||
| 172 | t.Run("a store failure fails closed", func(t *testing.T) { | 183 | t.Run("a store failure fails closed", func(t *testing.T) { | ||
| 184 | + | t.Parallel() | |||
| 173 | f := newPushFixture(t) | 185 | f := newPushFixture(t) | ||
| 174 | boom := errors.New("boom") | 186 | boom := errors.New("boom") | ||
| 175 | f.tokens.EXPECT().Get(ctx, "w1").Return(domain.Token{}, boom) | 187 | f.tokens.EXPECT().Get(ctx, "w1").Return(domain.Token{}, boom) | ||
| @@ -179,18 +191,21 @@ func TestPushesCheck(t *testing.T) { | |||||
| 179 | }) | 191 | }) | ||
| 180 | 192 | ||||
| 181 | t.Run("a token without write access is rejected", func(t *testing.T) { | 193 | t.Run("a token without write access is rejected", func(t *testing.T) { | ||
| 194 | + | t.Parallel() | |||
| 182 | f := newPushFixture(t) | 195 | f := newPushFixture(t) | ||
| 183 | f.tokens.EXPECT().Get(ctx, "r1").Return(domain.Token{ID: "r1", Scopes: scopes(domain.ScopeRepoRead)}, nil) | 196 | f.tokens.EXPECT().Get(ctx, "r1").Return(domain.Token{ID: "r1", Scopes: scopes(domain.ScopeRepoRead)}, nil) | ||
| 184 | assert.ErrorIs(t, f.pushes.Check(ctx, reader, n, ups), domain.ErrRejected) | 197 | assert.ErrorIs(t, f.pushes.Check(ctx, reader, n, ups), domain.ErrRejected) | ||
| 185 | }) | 198 | }) | ||
| 186 | 199 | ||||
| 187 | t.Run("anonymous is rejected", func(t *testing.T) { | 200 | t.Run("anonymous is rejected", func(t *testing.T) { | ||
| 201 | + | t.Parallel() | |||
| 188 | f := newPushFixture(t) | 202 | f := newPushFixture(t) | ||
| 189 | assert.ErrorIs(t, f.pushes.Check(ctx, domain.Anonymous(), n, ups), domain.ErrRejected) | 203 | assert.ErrorIs(t, f.pushes.Check(ctx, domain.Anonymous(), n, ups), domain.ErrRejected) | ||
| 190 | }) | 204 | }) | ||
| 191 | } | 205 | } | ||
| 192 | 206 | ||||
| 193 | func TestPushesRecord(t *testing.T) { | 207 | func TestPushesRecord(t *testing.T) { | ||
| 208 | + | t.Parallel() | |||
| 194 | ctx := context.Background() | 209 | ctx := context.Background() | ||
| 195 | n := name(t, "tools") | 210 | n := name(t, "tools") | ||
| 196 | p := tokenPrincipal(domain.ScopeRepoWrite) | 211 | p := tokenPrincipal(domain.ScopeRepoWrite) | ||
| @@ -201,6 +216,7 @@ func TestPushesRecord(t *testing.T) { | |||||
| 201 | } | 216 | } | ||
| 202 | 217 | ||||
| 203 | t.Run("sets pushed_at, publishes the exact updates, schedules maintenance", func(t *testing.T) { | 218 | t.Run("sets pushed_at, publishes the exact updates, schedules maintenance", func(t *testing.T) { | ||
| 219 | + | t.Parallel() | |||
| 204 | f := newPushFixture(t) | 220 | f := newPushFixture(t) | ||
| 205 | f.sched.EXPECT().Schedule(ctx, n).Return().Once() | 221 | f.sched.EXPECT().Schedule(ctx, n).Return().Once() | ||
| 206 | repo := domain.Repo{Name: n, Visibility: domain.VisibilityPrivate, CreatedAt: now.Add(-time.Hour)} | 222 | repo := domain.Repo{Name: n, Visibility: domain.VisibilityPrivate, CreatedAt: now.Add(-time.Hour)} | ||
| @@ -213,6 +229,7 @@ func TestPushesRecord(t *testing.T) { | |||||
| 213 | }) | 229 | }) | ||
| 214 | 230 | ||||
| 215 | t.Run("tells the reader that the repository changed", func(t *testing.T) { | 231 | t.Run("tells the reader that the repository changed", func(t *testing.T) { | ||
| 232 | + | t.Parallel() | |||
| 216 | f := newPushFixture(t) | 233 | f := newPushFixture(t) | ||
| 217 | f.sched.EXPECT().Schedule(ctx, n).Return().Once() | 234 | f.sched.EXPECT().Schedule(ctx, n).Return().Once() | ||
| 218 | fresh := mocks.NewRepoFreshener(t) | 235 | fresh := mocks.NewRepoFreshener(t) | ||
| @@ -226,6 +243,7 @@ func TestPushesRecord(t *testing.T) { | |||||
| 226 | }) | 243 | }) | ||
| 227 | 244 | ||||
| 228 | t.Run("pushed_at retries after a conflict and never goes backwards", func(t *testing.T) { | 245 | t.Run("pushed_at retries after a conflict and never goes backwards", func(t *testing.T) { | ||
| 246 | + | t.Parallel() | |||
| 229 | f := newPushFixture(t) | 247 | f := newPushFixture(t) | ||
| 230 | f.sched.EXPECT().Schedule(ctx, n).Return().Once() | 248 | f.sched.EXPECT().Schedule(ctx, n).Return().Once() | ||
| 231 | repo := domain.Repo{Name: n} | 249 | repo := domain.Repo{Name: n} | ||
| @@ -239,6 +257,7 @@ func TestPushesRecord(t *testing.T) { | |||||
| 239 | }) | 257 | }) | ||
| 240 | 258 | ||||
| 241 | t.Run("a deleted repository is not an error", func(t *testing.T) { | 259 | t.Run("a deleted repository is not an error", func(t *testing.T) { | ||
| 260 | + | t.Parallel() | |||
| 242 | f := newPushFixture(t) | 261 | f := newPushFixture(t) | ||
| 243 | f.sched.EXPECT().Schedule(ctx, n).Return().Once() | 262 | f.sched.EXPECT().Schedule(ctx, n).Return().Once() | ||
| 244 | f.meta.EXPECT().Get(ctx, n).Return(domain.Repo{}, domain.ErrNotFound) | 263 | f.meta.EXPECT().Get(ctx, n).Return(domain.Repo{}, domain.ErrNotFound) | ||
| @@ -248,6 +267,7 @@ func TestPushesRecord(t *testing.T) { | |||||
| 248 | }) | 267 | }) | ||
| 249 | 268 | ||||
| 250 | t.Run("a repository deleted before the default branch step is not an error", func(t *testing.T) { | 269 | t.Run("a repository deleted before the default branch step is not an error", func(t *testing.T) { | ||
| 270 | + | t.Parallel() | |||
| 251 | f := newPushFixture(t) | 271 | f := newPushFixture(t) | ||
| 252 | f.sched.EXPECT().Schedule(ctx, n).Return().Once() | 272 | f.sched.EXPECT().Schedule(ctx, n).Return().Once() | ||
| 253 | f.meta.EXPECT().Get(ctx, n).Return(domain.Repo{}, domain.ErrNotFound) | 273 | f.meta.EXPECT().Get(ctx, n).Return(domain.Repo{}, domain.ErrNotFound) | ||
| @@ -257,6 +277,7 @@ func TestPushesRecord(t *testing.T) { | |||||
| 257 | }) | 277 | }) | ||
| 258 | 278 | ||||
| 259 | t.Run("a repository deleted before HEAD is set is not an error", func(t *testing.T) { | 279 | t.Run("a repository deleted before HEAD is set is not an error", func(t *testing.T) { | ||
| 280 | + | t.Parallel() | |||
| 260 | f := newPushFixture(t) | 281 | f := newPushFixture(t) | ||
| 261 | f.sched.EXPECT().Schedule(ctx, n).Return().Once() | 282 | f.sched.EXPECT().Schedule(ctx, n).Return().Once() | ||
| 262 | f.meta.EXPECT().Get(ctx, n).Return(domain.Repo{}, domain.ErrNotFound) | 283 | f.meta.EXPECT().Get(ctx, n).Return(domain.Repo{}, domain.ErrNotFound) | ||
| @@ -267,6 +288,7 @@ func TestPushesRecord(t *testing.T) { | |||||
| 267 | }) | 288 | }) | ||
| 268 | 289 | ||||
| 269 | t.Run("every step runs when one fails", func(t *testing.T) { | 290 | t.Run("every step runs when one fails", func(t *testing.T) { | ||
| 291 | + | t.Parallel() | |||
| 270 | f := newPushFixture(t) | 292 | f := newPushFixture(t) | ||
| 271 | f.sched.EXPECT().Schedule(ctx, n).Return().Once() | 293 | f.sched.EXPECT().Schedule(ctx, n).Return().Once() | ||
| 272 | boom := errors.New("boom") | 294 | boom := errors.New("boom") | ||
| @@ -279,6 +301,7 @@ func TestPushesRecord(t *testing.T) { | |||||
| 279 | } | 301 | } | ||
| 280 | 302 | ||||
| 281 | func TestPushesDefaultBranchRule(t *testing.T) { | 303 | func TestPushesDefaultBranchRule(t *testing.T) { | ||
| 304 | + | t.Parallel() | |||
| 282 | ctx := context.Background() | 305 | ctx := context.Background() | ||
| 283 | n := name(t, "tools") | 306 | n := name(t, "tools") | ||
| 284 | p := domain.Root() | 307 | p := domain.Root() | ||
| @@ -303,6 +326,7 @@ func TestPushesDefaultBranchRule(t *testing.T) { | |||||
| 303 | } | 326 | } | ||
| 304 | for _, tt := range tests { | 327 | for _, tt := range tests { | ||
| 305 | t.Run(tt.name, func(t *testing.T) { | 328 | t.Run(tt.name, func(t *testing.T) { | ||
| 329 | + | t.Parallel() | |||
| 306 | f := newPushFixture(t) | 330 | f := newPushFixture(t) | ||
| 307 | f.sched.EXPECT().Schedule(ctx, n).Return().Once() | 331 | f.sched.EXPECT().Schedule(ctx, n).Return().Once() | ||
| 308 | f.meta.EXPECT().Get(ctx, n).Return(domain.Repo{Name: n, PushedAt: now}, nil) | 332 | f.meta.EXPECT().Get(ctx, n).Return(domain.Repo{Name: n, PushedAt: now}, nil) | ||
| @@ -316,6 +340,7 @@ func TestPushesDefaultBranchRule(t *testing.T) { | |||||
| 316 | } | 340 | } | ||
| 317 | 341 | ||||
| 318 | t.Run("set failure is reported", func(t *testing.T) { | 342 | t.Run("set failure is reported", func(t *testing.T) { | ||
| 343 | + | t.Parallel() | |||
| 319 | f := newPushFixture(t) | 344 | f := newPushFixture(t) | ||
| 320 | f.sched.EXPECT().Schedule(ctx, n).Return().Once() | 345 | f.sched.EXPECT().Schedule(ctx, n).Return().Once() | ||
| 321 | boom := errors.New("boom") | 346 | boom := errors.New("boom") | ||
M internal/app/repos_test.go +21 -0
| @@ -49,10 +49,12 @@ func name(t *testing.T, s string) domain.RepoName { | |||
| 49 | 49 | } | |
| 50 | 50 | ||
| 51 | 51 | func TestCreate(t *testing.T) { | |
| 52 | + | t.Parallel() | |
| 52 | 53 | ctx := context.Background() | |
| 53 | 54 | errBoom := errors.New("boom") | |
| 54 | 55 | ||
| 55 | 56 | t.Run("success is private by default", func(t *testing.T) { | |
| 57 | + | t.Parallel() | |
| 56 | 58 | f := newFixture(t) | |
| 57 | 59 | n := name(t, "tools") | |
| 58 | 60 | want := domain.Repo{Name: n, Description: "d", Visibility: domain.VisibilityPrivate, CreatedAt: now, Settings: domain.DefaultRepoSettings()} | |
| @@ -67,6 +69,7 @@ func TestCreate(t *testing.T) { | |||
| 67 | 69 | }) | |
| 68 | 70 | ||
| 69 | 71 | t.Run("anonymous is refused before anything runs", func(t *testing.T) { | |
| 72 | + | t.Parallel() | |
| 70 | 73 | f := newFixture(t) | |
| 71 | 74 | _, err := f.repos.Create(ctx, domain.Anonymous(), app.CreateParams{Name: name(t, "tools")}) | |
| 72 | 75 | assert.ErrorIs(t, err, domain.ErrUnauthenticated) | |
| @@ -84,6 +87,7 @@ func TestCreate(t *testing.T) { | |||
| 84 | 87 | }) | |
| 85 | 88 | ||
| 86 | 89 | t.Run("token with repo:write creates", func(t *testing.T) { | |
| 90 | + | t.Parallel() | |
| 87 | 91 | f := newFixture(t) | |
| 88 | 92 | n := name(t, "tools") | |
| 89 | 93 | f.git.EXPECT().Stage(ctx).Return(ports.StagedID("s1"), nil) | |
| @@ -95,18 +99,21 @@ func TestCreate(t *testing.T) { | |||
| 95 | 99 | }) | |
| 96 | 100 | ||
| 97 | 101 | t.Run("zero name is invalid", func(t *testing.T) { | |
| 102 | + | t.Parallel() | |
| 98 | 103 | f := newFixture(t) | |
| 99 | 104 | _, err := f.repos.Create(ctx, domain.Root(), app.CreateParams{}) | |
| 100 | 105 | assert.ErrorIs(t, err, domain.ErrInvalidName) | |
| 101 | 106 | }) | |
| 102 | 107 | ||
| 103 | 108 | t.Run("unknown visibility is invalid", func(t *testing.T) { | |
| 109 | + | t.Parallel() | |
| 104 | 110 | f := newFixture(t) | |
| 105 | 111 | _, err := f.repos.Create(ctx, domain.Root(), app.CreateParams{Name: name(t, "tools"), Visibility: "secret"}) | |
| 106 | 112 | assert.ErrorIs(t, err, domain.ErrInvalidVisibility) | |
| 107 | 113 | }) | |
| 108 | 114 | ||
| 109 | 115 | t.Run("stage failure stores nothing", func(t *testing.T) { | |
| 116 | + | t.Parallel() | |
| 110 | 117 | f := newFixture(t) | |
| 111 | 118 | f.git.EXPECT().Stage(ctx).Return("", errBoom) | |
| 112 | 119 | _, err := f.repos.Create(ctx, domain.Root(), app.CreateParams{Name: name(t, "tools")}) | |
| @@ -114,6 +121,7 @@ func TestCreate(t *testing.T) { | |||
| 114 | 121 | }) | |
| 115 | 122 | ||
| 116 | 123 | t.Run("existing name discards the staged repository", func(t *testing.T) { | |
| 124 | + | t.Parallel() | |
| 117 | 125 | f := newFixture(t) | |
| 118 | 126 | f.git.EXPECT().Stage(ctx).Return(ports.StagedID("s1"), nil) | |
| 119 | 127 | f.meta.EXPECT().Create(ctx, mock.Anything).Return(domain.ErrAlreadyExists) | |
| @@ -123,6 +131,7 @@ func TestCreate(t *testing.T) { | |||
| 123 | 131 | }) | |
| 124 | 132 | ||
| 125 | 133 | t.Run("publish conflict removes metadata and staging", func(t *testing.T) { | |
| 134 | + | t.Parallel() | |
| 126 | 135 | f := newFixture(t) | |
| 127 | 136 | n := name(t, "tools") | |
| 128 | 137 | f.git.EXPECT().Stage(ctx).Return(ports.StagedID("s1"), nil) | |
| @@ -135,6 +144,7 @@ func TestCreate(t *testing.T) { | |||
| 135 | 144 | }) | |
| 136 | 145 | ||
| 137 | 146 | t.Run("failed publish removes metadata and staging", func(t *testing.T) { | |
| 147 | + | t.Parallel() | |
| 138 | 148 | f := newFixture(t) | |
| 139 | 149 | n := name(t, "tools") | |
| 140 | 150 | f.git.EXPECT().Stage(ctx).Return(ports.StagedID("s1"), nil) | |
| @@ -147,6 +157,7 @@ func TestCreate(t *testing.T) { | |||
| 147 | 157 | }) | |
| 148 | 158 | ||
| 149 | 159 | t.Run("cleanup survives a canceled context", func(t *testing.T) { | |
| 160 | + | t.Parallel() | |
| 150 | 161 | f := newFixture(t) | |
| 151 | 162 | n := name(t, "tools") | |
| 152 | 163 | cctx, cancel := context.WithCancel(ctx) | |
| @@ -181,6 +192,7 @@ func tokenPrincipal(s domain.Scope, repos ...string) domain.Principal { | |||
| 181 | 192 | } | |
| 182 | 193 | ||
| 183 | 194 | func TestGetAndListAccess(t *testing.T) { | |
| 195 | + | t.Parallel() | |
| 184 | 196 | ctx := context.Background() | |
| 185 | 197 | pub := domain.Repo{Name: name(t, "pub"), Visibility: domain.VisibilityPublic} | |
| 186 | 198 | priv := domain.Repo{Name: name(t, "priv"), Visibility: domain.VisibilityPrivate} | |
| @@ -204,6 +216,7 @@ func TestGetAndListAccess(t *testing.T) { | |||
| 204 | 216 | } | |
| 205 | 217 | for _, tt := range tests { | |
| 206 | 218 | t.Run("get/"+tt.name, func(t *testing.T) { | |
| 219 | + | t.Parallel() | |
| 207 | 220 | f := newFixture(t) | |
| 208 | 221 | f.meta.EXPECT().Get(ctx, tt.get.Name).Return(tt.get, nil) | |
| 209 | 222 | got, err := f.repos.Get(ctx, tt.principal, tt.get.Name) | |
| @@ -218,6 +231,7 @@ func TestGetAndListAccess(t *testing.T) { | |||
| 218 | 231 | } | |
| 219 | 232 | ||
| 220 | 233 | t.Run("get missing is not found", func(t *testing.T) { | |
| 234 | + | t.Parallel() | |
| 221 | 235 | f := newFixture(t) | |
| 222 | 236 | f.meta.EXPECT().Get(ctx, pub.Name).Return(domain.Repo{}, domain.ErrNotFound) | |
| 223 | 237 | _, err := f.repos.Get(ctx, domain.Root(), pub.Name) | |
| @@ -225,6 +239,7 @@ func TestGetAndListAccess(t *testing.T) { | |||
| 225 | 239 | }) | |
| 226 | 240 | ||
| 227 | 241 | t.Run("private missing and hidden look the same", func(t *testing.T) { | |
| 242 | + | t.Parallel() | |
| 228 | 243 | f := newFixture(t) | |
| 229 | 244 | f.meta.EXPECT().Get(ctx, priv.Name).Return(priv, nil) | |
| 230 | 245 | _, errHidden := f.repos.Get(ctx, domain.Anonymous(), priv.Name) | |
| @@ -243,6 +258,7 @@ func TestGetAndListAccess(t *testing.T) { | |||
| 243 | 258 | } | |
| 244 | 259 | for _, tt := range listTests { | |
| 245 | 260 | t.Run("list/"+tt.name, func(t *testing.T) { | |
| 261 | + | t.Parallel() | |
| 246 | 262 | f := newFixture(t) | |
| 247 | 263 | f.meta.EXPECT().List(ctx).Return([]domain.Repo{priv, pub}, nil) | |
| 248 | 264 | got, err := f.repos.List(ctx, tt.principal) | |
| @@ -252,6 +268,7 @@ func TestGetAndListAccess(t *testing.T) { | |||
| 252 | 268 | } | |
| 253 | 269 | ||
| 254 | 270 | t.Run("list error", func(t *testing.T) { | |
| 271 | + | t.Parallel() | |
| 255 | 272 | f := newFixture(t) | |
| 256 | 273 | boom := errors.New("boom") | |
| 257 | 274 | f.meta.EXPECT().List(ctx).Return(nil, boom) | |
| @@ -261,9 +278,11 @@ func TestGetAndListAccess(t *testing.T) { | |||
| 261 | 278 | } | |
| 262 | 279 | ||
| 263 | 280 | func TestReconcile(t *testing.T) { | |
| 281 | + | t.Parallel() | |
| 264 | 282 | ctx := context.Background() | |
| 265 | 283 | ||
| 266 | 284 | t.Run("registers orphan dir as private, reports missing dir, skips invalid", func(t *testing.T) { | |
| 285 | + | t.Parallel() | |
| 267 | 286 | f := newFixture(t) | |
| 268 | 287 | orphan, kept, ghost := name(t, "orphan"), name(t, "kept"), name(t, "ghost") | |
| 269 | 288 | f.git.EXPECT().PurgeStaging(ctx).Return(nil) | |
| @@ -285,6 +304,7 @@ func TestReconcile(t *testing.T) { | |||
| 285 | 304 | }) | |
| 286 | 305 | ||
| 287 | 306 | t.Run("purge failure stops", func(t *testing.T) { | |
| 307 | + | t.Parallel() | |
| 288 | 308 | f := newFixture(t) | |
| 289 | 309 | boom := errors.New("boom") | |
| 290 | 310 | f.git.EXPECT().PurgeStaging(ctx).Return(boom) | |
| @@ -293,6 +313,7 @@ func TestReconcile(t *testing.T) { | |||
| 293 | 313 | }) | |
| 294 | 314 | ||
| 295 | 315 | t.Run("register failure stops", func(t *testing.T) { | |
| 316 | + | t.Parallel() | |
| 296 | 317 | f := newFixture(t) | |
| 297 | 318 | boom := errors.New("boom") | |
| 298 | 319 | orphan := name(t, "orphan") | |
| @@ -49,10 +49,12 @@ func name(t *testing.T, s string) domain.RepoName { | |||||
| 49 | } | 49 | } | ||
| 50 | 50 | ||||
| 51 | func TestCreate(t *testing.T) { | 51 | func TestCreate(t *testing.T) { | ||
| 52 | + | t.Parallel() | |||
| 52 | ctx := context.Background() | 53 | ctx := context.Background() | ||
| 53 | errBoom := errors.New("boom") | 54 | errBoom := errors.New("boom") | ||
| 54 | 55 | ||||
| 55 | t.Run("success is private by default", func(t *testing.T) { | 56 | t.Run("success is private by default", func(t *testing.T) { | ||
| 57 | + | t.Parallel() | |||
| 56 | f := newFixture(t) | 58 | f := newFixture(t) | ||
| 57 | n := name(t, "tools") | 59 | n := name(t, "tools") | ||
| 58 | want := domain.Repo{Name: n, Description: "d", Visibility: domain.VisibilityPrivate, CreatedAt: now, Settings: domain.DefaultRepoSettings()} | 60 | want := domain.Repo{Name: n, Description: "d", Visibility: domain.VisibilityPrivate, CreatedAt: now, Settings: domain.DefaultRepoSettings()} | ||
| @@ -67,6 +69,7 @@ func TestCreate(t *testing.T) { | |||||
| 67 | }) | 69 | }) | ||
| 68 | 70 | ||||
| 69 | t.Run("anonymous is refused before anything runs", func(t *testing.T) { | 71 | t.Run("anonymous is refused before anything runs", func(t *testing.T) { | ||
| 72 | + | t.Parallel() | |||
| 70 | f := newFixture(t) | 73 | f := newFixture(t) | ||
| 71 | _, err := f.repos.Create(ctx, domain.Anonymous(), app.CreateParams{Name: name(t, "tools")}) | 74 | _, err := f.repos.Create(ctx, domain.Anonymous(), app.CreateParams{Name: name(t, "tools")}) | ||
| 72 | assert.ErrorIs(t, err, domain.ErrUnauthenticated) | 75 | assert.ErrorIs(t, err, domain.ErrUnauthenticated) | ||
| @@ -84,6 +87,7 @@ func TestCreate(t *testing.T) { | |||||
| 84 | }) | 87 | }) | ||
| 85 | 88 | ||||
| 86 | t.Run("token with repo:write creates", func(t *testing.T) { | 89 | t.Run("token with repo:write creates", func(t *testing.T) { | ||
| 90 | + | t.Parallel() | |||
| 87 | f := newFixture(t) | 91 | f := newFixture(t) | ||
| 88 | n := name(t, "tools") | 92 | n := name(t, "tools") | ||
| 89 | f.git.EXPECT().Stage(ctx).Return(ports.StagedID("s1"), nil) | 93 | f.git.EXPECT().Stage(ctx).Return(ports.StagedID("s1"), nil) | ||
| @@ -95,18 +99,21 @@ func TestCreate(t *testing.T) { | |||||
| 95 | }) | 99 | }) | ||
| 96 | 100 | ||||
| 97 | t.Run("zero name is invalid", func(t *testing.T) { | 101 | t.Run("zero name is invalid", func(t *testing.T) { | ||
| 102 | + | t.Parallel() | |||
| 98 | f := newFixture(t) | 103 | f := newFixture(t) | ||
| 99 | _, err := f.repos.Create(ctx, domain.Root(), app.CreateParams{}) | 104 | _, err := f.repos.Create(ctx, domain.Root(), app.CreateParams{}) | ||
| 100 | assert.ErrorIs(t, err, domain.ErrInvalidName) | 105 | assert.ErrorIs(t, err, domain.ErrInvalidName) | ||
| 101 | }) | 106 | }) | ||
| 102 | 107 | ||||
| 103 | t.Run("unknown visibility is invalid", func(t *testing.T) { | 108 | t.Run("unknown visibility is invalid", func(t *testing.T) { | ||
| 109 | + | t.Parallel() | |||
| 104 | f := newFixture(t) | 110 | f := newFixture(t) | ||
| 105 | _, err := f.repos.Create(ctx, domain.Root(), app.CreateParams{Name: name(t, "tools"), Visibility: "secret"}) | 111 | _, err := f.repos.Create(ctx, domain.Root(), app.CreateParams{Name: name(t, "tools"), Visibility: "secret"}) | ||
| 106 | assert.ErrorIs(t, err, domain.ErrInvalidVisibility) | 112 | assert.ErrorIs(t, err, domain.ErrInvalidVisibility) | ||
| 107 | }) | 113 | }) | ||
| 108 | 114 | ||||
| 109 | t.Run("stage failure stores nothing", func(t *testing.T) { | 115 | t.Run("stage failure stores nothing", func(t *testing.T) { | ||
| 116 | + | t.Parallel() | |||
| 110 | f := newFixture(t) | 117 | f := newFixture(t) | ||
| 111 | f.git.EXPECT().Stage(ctx).Return("", errBoom) | 118 | f.git.EXPECT().Stage(ctx).Return("", errBoom) | ||
| 112 | _, err := f.repos.Create(ctx, domain.Root(), app.CreateParams{Name: name(t, "tools")}) | 119 | _, err := f.repos.Create(ctx, domain.Root(), app.CreateParams{Name: name(t, "tools")}) | ||
| @@ -114,6 +121,7 @@ func TestCreate(t *testing.T) { | |||||
| 114 | }) | 121 | }) | ||
| 115 | 122 | ||||
| 116 | t.Run("existing name discards the staged repository", func(t *testing.T) { | 123 | t.Run("existing name discards the staged repository", func(t *testing.T) { | ||
| 124 | + | t.Parallel() | |||
| 117 | f := newFixture(t) | 125 | f := newFixture(t) | ||
| 118 | f.git.EXPECT().Stage(ctx).Return(ports.StagedID("s1"), nil) | 126 | f.git.EXPECT().Stage(ctx).Return(ports.StagedID("s1"), nil) | ||
| 119 | f.meta.EXPECT().Create(ctx, mock.Anything).Return(domain.ErrAlreadyExists) | 127 | f.meta.EXPECT().Create(ctx, mock.Anything).Return(domain.ErrAlreadyExists) | ||
| @@ -123,6 +131,7 @@ func TestCreate(t *testing.T) { | |||||
| 123 | }) | 131 | }) | ||
| 124 | 132 | ||||
| 125 | t.Run("publish conflict removes metadata and staging", func(t *testing.T) { | 133 | t.Run("publish conflict removes metadata and staging", func(t *testing.T) { | ||
| 134 | + | t.Parallel() | |||
| 126 | f := newFixture(t) | 135 | f := newFixture(t) | ||
| 127 | n := name(t, "tools") | 136 | n := name(t, "tools") | ||
| 128 | f.git.EXPECT().Stage(ctx).Return(ports.StagedID("s1"), nil) | 137 | f.git.EXPECT().Stage(ctx).Return(ports.StagedID("s1"), nil) | ||
| @@ -135,6 +144,7 @@ func TestCreate(t *testing.T) { | |||||
| 135 | }) | 144 | }) | ||
| 136 | 145 | ||||
| 137 | t.Run("failed publish removes metadata and staging", func(t *testing.T) { | 146 | t.Run("failed publish removes metadata and staging", func(t *testing.T) { | ||
| 147 | + | t.Parallel() | |||
| 138 | f := newFixture(t) | 148 | f := newFixture(t) | ||
| 139 | n := name(t, "tools") | 149 | n := name(t, "tools") | ||
| 140 | f.git.EXPECT().Stage(ctx).Return(ports.StagedID("s1"), nil) | 150 | f.git.EXPECT().Stage(ctx).Return(ports.StagedID("s1"), nil) | ||
| @@ -147,6 +157,7 @@ func TestCreate(t *testing.T) { | |||||
| 147 | }) | 157 | }) | ||
| 148 | 158 | ||||
| 149 | t.Run("cleanup survives a canceled context", func(t *testing.T) { | 159 | t.Run("cleanup survives a canceled context", func(t *testing.T) { | ||
| 160 | + | t.Parallel() | |||
| 150 | f := newFixture(t) | 161 | f := newFixture(t) | ||
| 151 | n := name(t, "tools") | 162 | n := name(t, "tools") | ||
| 152 | cctx, cancel := context.WithCancel(ctx) | 163 | cctx, cancel := context.WithCancel(ctx) | ||
| @@ -181,6 +192,7 @@ func tokenPrincipal(s domain.Scope, repos ...string) domain.Principal { | |||||
| 181 | } | 192 | } | ||
| 182 | 193 | ||||
| 183 | func TestGetAndListAccess(t *testing.T) { | 194 | func TestGetAndListAccess(t *testing.T) { | ||
| 195 | + | t.Parallel() | |||
| 184 | ctx := context.Background() | 196 | ctx := context.Background() | ||
| 185 | pub := domain.Repo{Name: name(t, "pub"), Visibility: domain.VisibilityPublic} | 197 | pub := domain.Repo{Name: name(t, "pub"), Visibility: domain.VisibilityPublic} | ||
| 186 | priv := domain.Repo{Name: name(t, "priv"), Visibility: domain.VisibilityPrivate} | 198 | priv := domain.Repo{Name: name(t, "priv"), Visibility: domain.VisibilityPrivate} | ||
| @@ -204,6 +216,7 @@ func TestGetAndListAccess(t *testing.T) { | |||||
| 204 | } | 216 | } | ||
| 205 | for _, tt := range tests { | 217 | for _, tt := range tests { | ||
| 206 | t.Run("get/"+tt.name, func(t *testing.T) { | 218 | t.Run("get/"+tt.name, func(t *testing.T) { | ||
| 219 | + | t.Parallel() | |||
| 207 | f := newFixture(t) | 220 | f := newFixture(t) | ||
| 208 | f.meta.EXPECT().Get(ctx, tt.get.Name).Return(tt.get, nil) | 221 | f.meta.EXPECT().Get(ctx, tt.get.Name).Return(tt.get, nil) | ||
| 209 | got, err := f.repos.Get(ctx, tt.principal, tt.get.Name) | 222 | got, err := f.repos.Get(ctx, tt.principal, tt.get.Name) | ||
| @@ -218,6 +231,7 @@ func TestGetAndListAccess(t *testing.T) { | |||||
| 218 | } | 231 | } | ||
| 219 | 232 | ||||
| 220 | t.Run("get missing is not found", func(t *testing.T) { | 233 | t.Run("get missing is not found", func(t *testing.T) { | ||
| 234 | + | t.Parallel() | |||
| 221 | f := newFixture(t) | 235 | f := newFixture(t) | ||
| 222 | f.meta.EXPECT().Get(ctx, pub.Name).Return(domain.Repo{}, domain.ErrNotFound) | 236 | f.meta.EXPECT().Get(ctx, pub.Name).Return(domain.Repo{}, domain.ErrNotFound) | ||
| 223 | _, err := f.repos.Get(ctx, domain.Root(), pub.Name) | 237 | _, err := f.repos.Get(ctx, domain.Root(), pub.Name) | ||
| @@ -225,6 +239,7 @@ func TestGetAndListAccess(t *testing.T) { | |||||
| 225 | }) | 239 | }) | ||
| 226 | 240 | ||||
| 227 | t.Run("private missing and hidden look the same", func(t *testing.T) { | 241 | t.Run("private missing and hidden look the same", func(t *testing.T) { | ||
| 242 | + | t.Parallel() | |||
| 228 | f := newFixture(t) | 243 | f := newFixture(t) | ||
| 229 | f.meta.EXPECT().Get(ctx, priv.Name).Return(priv, nil) | 244 | f.meta.EXPECT().Get(ctx, priv.Name).Return(priv, nil) | ||
| 230 | _, errHidden := f.repos.Get(ctx, domain.Anonymous(), priv.Name) | 245 | _, errHidden := f.repos.Get(ctx, domain.Anonymous(), priv.Name) | ||
| @@ -243,6 +258,7 @@ func TestGetAndListAccess(t *testing.T) { | |||||
| 243 | } | 258 | } | ||
| 244 | for _, tt := range listTests { | 259 | for _, tt := range listTests { | ||
| 245 | t.Run("list/"+tt.name, func(t *testing.T) { | 260 | t.Run("list/"+tt.name, func(t *testing.T) { | ||
| 261 | + | t.Parallel() | |||
| 246 | f := newFixture(t) | 262 | f := newFixture(t) | ||
| 247 | f.meta.EXPECT().List(ctx).Return([]domain.Repo{priv, pub}, nil) | 263 | f.meta.EXPECT().List(ctx).Return([]domain.Repo{priv, pub}, nil) | ||
| 248 | got, err := f.repos.List(ctx, tt.principal) | 264 | got, err := f.repos.List(ctx, tt.principal) | ||
| @@ -252,6 +268,7 @@ func TestGetAndListAccess(t *testing.T) { | |||||
| 252 | } | 268 | } | ||
| 253 | 269 | ||||
| 254 | t.Run("list error", func(t *testing.T) { | 270 | t.Run("list error", func(t *testing.T) { | ||
| 271 | + | t.Parallel() | |||
| 255 | f := newFixture(t) | 272 | f := newFixture(t) | ||
| 256 | boom := errors.New("boom") | 273 | boom := errors.New("boom") | ||
| 257 | f.meta.EXPECT().List(ctx).Return(nil, boom) | 274 | f.meta.EXPECT().List(ctx).Return(nil, boom) | ||
| @@ -261,9 +278,11 @@ func TestGetAndListAccess(t *testing.T) { | |||||
| 261 | } | 278 | } | ||
| 262 | 279 | ||||
| 263 | func TestReconcile(t *testing.T) { | 280 | func TestReconcile(t *testing.T) { | ||
| 281 | + | t.Parallel() | |||
| 264 | ctx := context.Background() | 282 | ctx := context.Background() | ||
| 265 | 283 | ||||
| 266 | t.Run("registers orphan dir as private, reports missing dir, skips invalid", func(t *testing.T) { | 284 | t.Run("registers orphan dir as private, reports missing dir, skips invalid", func(t *testing.T) { | ||
| 285 | + | t.Parallel() | |||
| 267 | f := newFixture(t) | 286 | f := newFixture(t) | ||
| 268 | orphan, kept, ghost := name(t, "orphan"), name(t, "kept"), name(t, "ghost") | 287 | orphan, kept, ghost := name(t, "orphan"), name(t, "kept"), name(t, "ghost") | ||
| 269 | f.git.EXPECT().PurgeStaging(ctx).Return(nil) | 288 | f.git.EXPECT().PurgeStaging(ctx).Return(nil) | ||
| @@ -285,6 +304,7 @@ func TestReconcile(t *testing.T) { | |||||
| 285 | }) | 304 | }) | ||
| 286 | 305 | ||||
| 287 | t.Run("purge failure stops", func(t *testing.T) { | 306 | t.Run("purge failure stops", func(t *testing.T) { | ||
| 307 | + | t.Parallel() | |||
| 288 | f := newFixture(t) | 308 | f := newFixture(t) | ||
| 289 | boom := errors.New("boom") | 309 | boom := errors.New("boom") | ||
| 290 | f.git.EXPECT().PurgeStaging(ctx).Return(boom) | 310 | f.git.EXPECT().PurgeStaging(ctx).Return(boom) | ||
| @@ -293,6 +313,7 @@ func TestReconcile(t *testing.T) { | |||||
| 293 | }) | 313 | }) | ||
| 294 | 314 | ||||
| 295 | t.Run("register failure stops", func(t *testing.T) { | 315 | t.Run("register failure stops", func(t *testing.T) { | ||
| 316 | + | t.Parallel() | |||
| 296 | f := newFixture(t) | 317 | f := newFixture(t) | ||
| 297 | boom := errors.New("boom") | 318 | boom := errors.New("boom") | ||
| 298 | orphan := name(t, "orphan") | 319 | orphan := name(t, "orphan") | ||
M internal/app/repos_write_test.go +18 -0
| @@ -20,12 +20,14 @@ import ( | |||
| 20 | 20 | func ptr[T any](v T) *T { return &v } | |
| 21 | 21 | ||
| 22 | 22 | func TestUpdate(t *testing.T) { | |
| 23 | + | t.Parallel() | |
| 23 | 24 | ctx := context.Background() | |
| 24 | 25 | n := name(t, "tools") | |
| 25 | 26 | priv := domain.Repo{Name: n, Visibility: domain.VisibilityPrivate, Description: "old"} | |
| 26 | 27 | admin := tokenPrincipal(domain.ScopeRepoAdmin, "tools") | |
| 27 | 28 | ||
| 28 | 29 | t.Run("description and visibility are stored and RepoUpdated is published", func(t *testing.T) { | |
| 30 | + | t.Parallel() | |
| 29 | 31 | f := newFixture(t) | |
| 30 | 32 | f.meta.EXPECT().Get(ctx, n).Return(priv, nil).Twice() | |
| 31 | 33 | want := domain.Repo{Name: n, Visibility: domain.VisibilityPublic, Description: "new", Settings: domain.DefaultRepoSettings()} | |
| @@ -42,6 +44,7 @@ func TestUpdate(t *testing.T) { | |||
| 42 | 44 | }) | |
| 43 | 45 | ||
| 44 | 46 | t.Run("a lost race on the metadata is retried", func(t *testing.T) { | |
| 47 | + | t.Parallel() | |
| 45 | 48 | f := newFixture(t) | |
| 46 | 49 | f.meta.EXPECT().Get(ctx, n).Return(priv, nil) | |
| 47 | 50 | f.meta.EXPECT().Update(ctx, mock.Anything).Return(domain.ErrConflict).Once() | |
| @@ -52,6 +55,7 @@ func TestUpdate(t *testing.T) { | |||
| 52 | 55 | }) | |
| 53 | 56 | ||
| 54 | 57 | t.Run("the default branch is checked, then HEAD moves", func(t *testing.T) { | |
| 58 | + | t.Parallel() | |
| 55 | 59 | f := newFixture(t) | |
| 56 | 60 | f.meta.EXPECT().Get(ctx, n).Return(priv, nil) | |
| 57 | 61 | f.git.EXPECT().HasBranch(ctx, n, "dev").Return(true, nil) | |
| @@ -63,6 +67,7 @@ func TestUpdate(t *testing.T) { | |||
| 63 | 67 | }) | |
| 64 | 68 | ||
| 65 | 69 | t.Run("metadata is written before HEAD moves, and the event follows both", func(t *testing.T) { | |
| 70 | + | t.Parallel() | |
| 66 | 71 | f := newFixture(t) | |
| 67 | 72 | var order []string | |
| 68 | 73 | f.meta.EXPECT().Get(ctx, n).Return(priv, nil) | |
| @@ -82,6 +87,7 @@ func TestUpdate(t *testing.T) { | |||
| 82 | 87 | }) | |
| 83 | 88 | ||
| 84 | 89 | t.Run("a HEAD that cannot move after the metadata was stored still publishes, then fails", func(t *testing.T) { | |
| 90 | + | t.Parallel() | |
| 85 | 91 | f := newFixture(t) | |
| 86 | 92 | boom := errors.New("boom") | |
| 87 | 93 | pub := domain.Repo{Name: n, Visibility: domain.VisibilityPublic, Description: "old"} | |
| @@ -97,6 +103,7 @@ func TestUpdate(t *testing.T) { | |||
| 97 | 103 | }) | |
| 98 | 104 | ||
| 99 | 105 | t.Run("a HEAD that cannot move with no metadata change publishes nothing", func(t *testing.T) { | |
| 106 | + | t.Parallel() | |
| 100 | 107 | f := newFixture(t) | |
| 101 | 108 | boom := errors.New("boom") | |
| 102 | 109 | f.meta.EXPECT().Get(ctx, n).Return(priv, nil) | |
| @@ -107,6 +114,7 @@ func TestUpdate(t *testing.T) { | |||
| 107 | 114 | }) | |
| 108 | 115 | ||
| 109 | 116 | t.Run("a missing branch is a failed precondition and HEAD stays", func(t *testing.T) { | |
| 117 | + | t.Parallel() | |
| 110 | 118 | f := newFixture(t) | |
| 111 | 119 | f.meta.EXPECT().Get(ctx, n).Return(priv, nil) | |
| 112 | 120 | f.git.EXPECT().HasBranch(ctx, n, "nope").Return(false, nil) | |
| @@ -146,6 +154,7 @@ func TestUpdate(t *testing.T) { | |||
| 146 | 154 | } | |
| 147 | 155 | for _, tt := range access { | |
| 148 | 156 | t.Run(tt.name, func(t *testing.T) { | |
| 157 | + | t.Parallel() | |
| 149 | 158 | f := newFixture(t) | |
| 150 | 159 | f.meta.EXPECT().Get(ctx, n).Return(tt.repo, nil) | |
| 151 | 160 | _, err := f.repos.Update(ctx, tt.p, n, app.UpdateParams{Description: ptr("x")}) | |
| @@ -156,6 +165,7 @@ func TestUpdate(t *testing.T) { | |||
| 156 | 165 | } | |
| 157 | 166 | ||
| 158 | 167 | t.Run("a missing repository is not found", func(t *testing.T) { | |
| 168 | + | t.Parallel() | |
| 159 | 169 | f := newFixture(t) | |
| 160 | 170 | f.meta.EXPECT().Get(ctx, n).Return(domain.Repo{}, domain.ErrNotFound) | |
| 161 | 171 | _, err := f.repos.Update(ctx, domain.Root(), n, app.UpdateParams{Description: ptr("x")}) | |
| @@ -164,11 +174,13 @@ func TestUpdate(t *testing.T) { | |||
| 164 | 174 | } | |
| 165 | 175 | ||
| 166 | 176 | func TestDelete(t *testing.T) { | |
| 177 | + | t.Parallel() | |
| 167 | 178 | ctx := context.Background() | |
| 168 | 179 | n := name(t, "tools") | |
| 169 | 180 | priv := domain.Repo{Name: n, Visibility: domain.VisibilityPrivate} | |
| 170 | 181 | ||
| 171 | 182 | t.Run("trash, forget the metadata, publish RepoDeleted with the visibility", func(t *testing.T) { | |
| 183 | + | t.Parallel() | |
| 172 | 184 | f := newFixture(t) | |
| 173 | 185 | f.meta.EXPECT().Get(ctx, n).Return(priv, nil) | |
| 174 | 186 | f.git.EXPECT().Trash(ctx, n).Return(nil) | |
| @@ -178,6 +190,7 @@ func TestDelete(t *testing.T) { | |||
| 178 | 190 | }) | |
| 179 | 191 | ||
| 180 | 192 | t.Run("a directory already gone still loses its metadata", func(t *testing.T) { | |
| 193 | + | t.Parallel() | |
| 181 | 194 | f := newFixture(t) | |
| 182 | 195 | f.meta.EXPECT().Get(ctx, n).Return(priv, nil) | |
| 183 | 196 | f.git.EXPECT().Trash(ctx, n).Return(domain.ErrNotFound) | |
| @@ -187,6 +200,7 @@ func TestDelete(t *testing.T) { | |||
| 187 | 200 | }) | |
| 188 | 201 | ||
| 189 | 202 | t.Run("a failed move keeps the metadata", func(t *testing.T) { | |
| 203 | + | t.Parallel() | |
| 190 | 204 | f := newFixture(t) | |
| 191 | 205 | boom := errors.New("boom") | |
| 192 | 206 | f.meta.EXPECT().Get(ctx, n).Return(priv, nil) | |
| @@ -196,6 +210,7 @@ func TestDelete(t *testing.T) { | |||
| 196 | 210 | } | |
| 197 | 211 | ||
| 198 | 212 | func TestView(t *testing.T) { | |
| 213 | + | t.Parallel() | |
| 199 | 214 | ctx := context.Background() | |
| 200 | 215 | n := name(t, "tools") | |
| 201 | 216 | repo := domain.Repo{Name: n} | |
| @@ -213,6 +228,7 @@ func TestView(t *testing.T) { | |||
| 213 | 228 | } | |
| 214 | 229 | ||
| 215 | 230 | func TestListPageSizes(t *testing.T) { | |
| 231 | + | t.Parallel() | |
| 216 | 232 | ctx := context.Background() | |
| 217 | 233 | var all []domain.Repo | |
| 218 | 234 | for i := range app.MaxRepoPageSize + 10 { | |
| @@ -230,6 +246,7 @@ func TestListPageSizes(t *testing.T) { | |||
| 230 | 246 | {"lowered to the maximum", app.MaxRepoPageSize * 10, app.MaxRepoPageSize, true}, | |
| 231 | 247 | } { | |
| 232 | 248 | t.Run(tt.name, func(t *testing.T) { | |
| 249 | + | t.Parallel() | |
| 233 | 250 | f := newFixture(t) | |
| 234 | 251 | f.meta.EXPECT().List(ctx).Return(all, nil) | |
| 235 | 252 | page, err := f.repos.ListPage(ctx, domain.Anonymous(), tt.size, "") | |
| @@ -241,6 +258,7 @@ func TestListPageSizes(t *testing.T) { | |||
| 241 | 258 | } | |
| 242 | 259 | ||
| 243 | 260 | func TestListPage(t *testing.T) { | |
| 261 | + | t.Parallel() | |
| 244 | 262 | ctx := context.Background() | |
| 245 | 263 | var all []domain.Repo | |
| 246 | 264 | for _, s := range []string{"a", "b", "c", "d", "e"} { | |
| @@ -20,12 +20,14 @@ import ( | |||||
| 20 | func ptr[T any](v T) *T { return &v } | 20 | func ptr[T any](v T) *T { return &v } | ||
| 21 | 21 | ||||
| 22 | func TestUpdate(t *testing.T) { | 22 | func TestUpdate(t *testing.T) { | ||
| 23 | + | t.Parallel() | |||
| 23 | ctx := context.Background() | 24 | ctx := context.Background() | ||
| 24 | n := name(t, "tools") | 25 | n := name(t, "tools") | ||
| 25 | priv := domain.Repo{Name: n, Visibility: domain.VisibilityPrivate, Description: "old"} | 26 | priv := domain.Repo{Name: n, Visibility: domain.VisibilityPrivate, Description: "old"} | ||
| 26 | admin := tokenPrincipal(domain.ScopeRepoAdmin, "tools") | 27 | admin := tokenPrincipal(domain.ScopeRepoAdmin, "tools") | ||
| 27 | 28 | ||||
| 28 | t.Run("description and visibility are stored and RepoUpdated is published", func(t *testing.T) { | 29 | t.Run("description and visibility are stored and RepoUpdated is published", func(t *testing.T) { | ||
| 30 | + | t.Parallel() | |||
| 29 | f := newFixture(t) | 31 | f := newFixture(t) | ||
| 30 | f.meta.EXPECT().Get(ctx, n).Return(priv, nil).Twice() | 32 | f.meta.EXPECT().Get(ctx, n).Return(priv, nil).Twice() | ||
| 31 | want := domain.Repo{Name: n, Visibility: domain.VisibilityPublic, Description: "new", Settings: domain.DefaultRepoSettings()} | 33 | want := domain.Repo{Name: n, Visibility: domain.VisibilityPublic, Description: "new", Settings: domain.DefaultRepoSettings()} | ||
| @@ -42,6 +44,7 @@ func TestUpdate(t *testing.T) { | |||||
| 42 | }) | 44 | }) | ||
| 43 | 45 | ||||
| 44 | t.Run("a lost race on the metadata is retried", func(t *testing.T) { | 46 | t.Run("a lost race on the metadata is retried", func(t *testing.T) { | ||
| 47 | + | t.Parallel() | |||
| 45 | f := newFixture(t) | 48 | f := newFixture(t) | ||
| 46 | f.meta.EXPECT().Get(ctx, n).Return(priv, nil) | 49 | f.meta.EXPECT().Get(ctx, n).Return(priv, nil) | ||
| 47 | f.meta.EXPECT().Update(ctx, mock.Anything).Return(domain.ErrConflict).Once() | 50 | f.meta.EXPECT().Update(ctx, mock.Anything).Return(domain.ErrConflict).Once() | ||
| @@ -52,6 +55,7 @@ func TestUpdate(t *testing.T) { | |||||
| 52 | }) | 55 | }) | ||
| 53 | 56 | ||||
| 54 | t.Run("the default branch is checked, then HEAD moves", func(t *testing.T) { | 57 | t.Run("the default branch is checked, then HEAD moves", func(t *testing.T) { | ||
| 58 | + | t.Parallel() | |||
| 55 | f := newFixture(t) | 59 | f := newFixture(t) | ||
| 56 | f.meta.EXPECT().Get(ctx, n).Return(priv, nil) | 60 | f.meta.EXPECT().Get(ctx, n).Return(priv, nil) | ||
| 57 | f.git.EXPECT().HasBranch(ctx, n, "dev").Return(true, nil) | 61 | f.git.EXPECT().HasBranch(ctx, n, "dev").Return(true, nil) | ||
| @@ -63,6 +67,7 @@ func TestUpdate(t *testing.T) { | |||||
| 63 | }) | 67 | }) | ||
| 64 | 68 | ||||
| 65 | t.Run("metadata is written before HEAD moves, and the event follows both", func(t *testing.T) { | 69 | t.Run("metadata is written before HEAD moves, and the event follows both", func(t *testing.T) { | ||
| 70 | + | t.Parallel() | |||
| 66 | f := newFixture(t) | 71 | f := newFixture(t) | ||
| 67 | var order []string | 72 | var order []string | ||
| 68 | f.meta.EXPECT().Get(ctx, n).Return(priv, nil) | 73 | f.meta.EXPECT().Get(ctx, n).Return(priv, nil) | ||
| @@ -82,6 +87,7 @@ func TestUpdate(t *testing.T) { | |||||
| 82 | }) | 87 | }) | ||
| 83 | 88 | ||||
| 84 | t.Run("a HEAD that cannot move after the metadata was stored still publishes, then fails", func(t *testing.T) { | 89 | t.Run("a HEAD that cannot move after the metadata was stored still publishes, then fails", func(t *testing.T) { | ||
| 90 | + | t.Parallel() | |||
| 85 | f := newFixture(t) | 91 | f := newFixture(t) | ||
| 86 | boom := errors.New("boom") | 92 | boom := errors.New("boom") | ||
| 87 | pub := domain.Repo{Name: n, Visibility: domain.VisibilityPublic, Description: "old"} | 93 | pub := domain.Repo{Name: n, Visibility: domain.VisibilityPublic, Description: "old"} | ||
| @@ -97,6 +103,7 @@ func TestUpdate(t *testing.T) { | |||||
| 97 | }) | 103 | }) | ||
| 98 | 104 | ||||
| 99 | t.Run("a HEAD that cannot move with no metadata change publishes nothing", func(t *testing.T) { | 105 | t.Run("a HEAD that cannot move with no metadata change publishes nothing", func(t *testing.T) { | ||
| 106 | + | t.Parallel() | |||
| 100 | f := newFixture(t) | 107 | f := newFixture(t) | ||
| 101 | boom := errors.New("boom") | 108 | boom := errors.New("boom") | ||
| 102 | f.meta.EXPECT().Get(ctx, n).Return(priv, nil) | 109 | f.meta.EXPECT().Get(ctx, n).Return(priv, nil) | ||
| @@ -107,6 +114,7 @@ func TestUpdate(t *testing.T) { | |||||
| 107 | }) | 114 | }) | ||
| 108 | 115 | ||||
| 109 | t.Run("a missing branch is a failed precondition and HEAD stays", func(t *testing.T) { | 116 | t.Run("a missing branch is a failed precondition and HEAD stays", func(t *testing.T) { | ||
| 117 | + | t.Parallel() | |||
| 110 | f := newFixture(t) | 118 | f := newFixture(t) | ||
| 111 | f.meta.EXPECT().Get(ctx, n).Return(priv, nil) | 119 | f.meta.EXPECT().Get(ctx, n).Return(priv, nil) | ||
| 112 | f.git.EXPECT().HasBranch(ctx, n, "nope").Return(false, nil) | 120 | f.git.EXPECT().HasBranch(ctx, n, "nope").Return(false, nil) | ||
| @@ -146,6 +154,7 @@ func TestUpdate(t *testing.T) { | |||||
| 146 | } | 154 | } | ||
| 147 | for _, tt := range access { | 155 | for _, tt := range access { | ||
| 148 | t.Run(tt.name, func(t *testing.T) { | 156 | t.Run(tt.name, func(t *testing.T) { | ||
| 157 | + | t.Parallel() | |||
| 149 | f := newFixture(t) | 158 | f := newFixture(t) | ||
| 150 | f.meta.EXPECT().Get(ctx, n).Return(tt.repo, nil) | 159 | f.meta.EXPECT().Get(ctx, n).Return(tt.repo, nil) | ||
| 151 | _, err := f.repos.Update(ctx, tt.p, n, app.UpdateParams{Description: ptr("x")}) | 160 | _, err := f.repos.Update(ctx, tt.p, n, app.UpdateParams{Description: ptr("x")}) | ||
| @@ -156,6 +165,7 @@ func TestUpdate(t *testing.T) { | |||||
| 156 | } | 165 | } | ||
| 157 | 166 | ||||
| 158 | t.Run("a missing repository is not found", func(t *testing.T) { | 167 | t.Run("a missing repository is not found", func(t *testing.T) { | ||
| 168 | + | t.Parallel() | |||
| 159 | f := newFixture(t) | 169 | f := newFixture(t) | ||
| 160 | f.meta.EXPECT().Get(ctx, n).Return(domain.Repo{}, domain.ErrNotFound) | 170 | f.meta.EXPECT().Get(ctx, n).Return(domain.Repo{}, domain.ErrNotFound) | ||
| 161 | _, err := f.repos.Update(ctx, domain.Root(), n, app.UpdateParams{Description: ptr("x")}) | 171 | _, err := f.repos.Update(ctx, domain.Root(), n, app.UpdateParams{Description: ptr("x")}) | ||
| @@ -164,11 +174,13 @@ func TestUpdate(t *testing.T) { | |||||
| 164 | } | 174 | } | ||
| 165 | 175 | ||||
| 166 | func TestDelete(t *testing.T) { | 176 | func TestDelete(t *testing.T) { | ||
| 177 | + | t.Parallel() | |||
| 167 | ctx := context.Background() | 178 | ctx := context.Background() | ||
| 168 | n := name(t, "tools") | 179 | n := name(t, "tools") | ||
| 169 | priv := domain.Repo{Name: n, Visibility: domain.VisibilityPrivate} | 180 | priv := domain.Repo{Name: n, Visibility: domain.VisibilityPrivate} | ||
| 170 | 181 | ||||
| 171 | t.Run("trash, forget the metadata, publish RepoDeleted with the visibility", func(t *testing.T) { | 182 | t.Run("trash, forget the metadata, publish RepoDeleted with the visibility", func(t *testing.T) { | ||
| 183 | + | t.Parallel() | |||
| 172 | f := newFixture(t) | 184 | f := newFixture(t) | ||
| 173 | f.meta.EXPECT().Get(ctx, n).Return(priv, nil) | 185 | f.meta.EXPECT().Get(ctx, n).Return(priv, nil) | ||
| 174 | f.git.EXPECT().Trash(ctx, n).Return(nil) | 186 | f.git.EXPECT().Trash(ctx, n).Return(nil) | ||
| @@ -178,6 +190,7 @@ func TestDelete(t *testing.T) { | |||||
| 178 | }) | 190 | }) | ||
| 179 | 191 | ||||
| 180 | t.Run("a directory already gone still loses its metadata", func(t *testing.T) { | 192 | t.Run("a directory already gone still loses its metadata", func(t *testing.T) { | ||
| 193 | + | t.Parallel() | |||
| 181 | f := newFixture(t) | 194 | f := newFixture(t) | ||
| 182 | f.meta.EXPECT().Get(ctx, n).Return(priv, nil) | 195 | f.meta.EXPECT().Get(ctx, n).Return(priv, nil) | ||
| 183 | f.git.EXPECT().Trash(ctx, n).Return(domain.ErrNotFound) | 196 | f.git.EXPECT().Trash(ctx, n).Return(domain.ErrNotFound) | ||
| @@ -187,6 +200,7 @@ func TestDelete(t *testing.T) { | |||||
| 187 | }) | 200 | }) | ||
| 188 | 201 | ||||
| 189 | t.Run("a failed move keeps the metadata", func(t *testing.T) { | 202 | t.Run("a failed move keeps the metadata", func(t *testing.T) { | ||
| 203 | + | t.Parallel() | |||
| 190 | f := newFixture(t) | 204 | f := newFixture(t) | ||
| 191 | boom := errors.New("boom") | 205 | boom := errors.New("boom") | ||
| 192 | f.meta.EXPECT().Get(ctx, n).Return(priv, nil) | 206 | f.meta.EXPECT().Get(ctx, n).Return(priv, nil) | ||
| @@ -196,6 +210,7 @@ func TestDelete(t *testing.T) { | |||||
| 196 | } | 210 | } | ||
| 197 | 211 | ||||
| 198 | func TestView(t *testing.T) { | 212 | func TestView(t *testing.T) { | ||
| 213 | + | t.Parallel() | |||
| 199 | ctx := context.Background() | 214 | ctx := context.Background() | ||
| 200 | n := name(t, "tools") | 215 | n := name(t, "tools") | ||
| 201 | repo := domain.Repo{Name: n} | 216 | repo := domain.Repo{Name: n} | ||
| @@ -213,6 +228,7 @@ func TestView(t *testing.T) { | |||||
| 213 | } | 228 | } | ||
| 214 | 229 | ||||
| 215 | func TestListPageSizes(t *testing.T) { | 230 | func TestListPageSizes(t *testing.T) { | ||
| 231 | + | t.Parallel() | |||
| 216 | ctx := context.Background() | 232 | ctx := context.Background() | ||
| 217 | var all []domain.Repo | 233 | var all []domain.Repo | ||
| 218 | for i := range app.MaxRepoPageSize + 10 { | 234 | for i := range app.MaxRepoPageSize + 10 { | ||
| @@ -230,6 +246,7 @@ func TestListPageSizes(t *testing.T) { | |||||
| 230 | {"lowered to the maximum", app.MaxRepoPageSize * 10, app.MaxRepoPageSize, true}, | 246 | {"lowered to the maximum", app.MaxRepoPageSize * 10, app.MaxRepoPageSize, true}, | ||
| 231 | } { | 247 | } { | ||
| 232 | t.Run(tt.name, func(t *testing.T) { | 248 | t.Run(tt.name, func(t *testing.T) { | ||
| 249 | + | t.Parallel() | |||
| 233 | f := newFixture(t) | 250 | f := newFixture(t) | ||
| 234 | f.meta.EXPECT().List(ctx).Return(all, nil) | 251 | f.meta.EXPECT().List(ctx).Return(all, nil) | ||
| 235 | page, err := f.repos.ListPage(ctx, domain.Anonymous(), tt.size, "") | 252 | page, err := f.repos.ListPage(ctx, domain.Anonymous(), tt.size, "") | ||
| @@ -241,6 +258,7 @@ func TestListPageSizes(t *testing.T) { | |||||
| 241 | } | 258 | } | ||
| 242 | 259 | ||||
| 243 | func TestListPage(t *testing.T) { | 260 | func TestListPage(t *testing.T) { | ||
| 261 | + | t.Parallel() | |||
| 244 | ctx := context.Background() | 262 | ctx := context.Background() | ||
| 245 | var all []domain.Repo | 263 | var all []domain.Repo | ||
| 246 | for _, s := range []string{"a", "b", "c", "d", "e"} { | 264 | for _, s := range []string{"a", "b", "c", "d", "e"} { | ||
M internal/app/sessions_test.go +8 -0
| @@ -72,6 +72,7 @@ func allScopeToken(repos ...domain.RepoName) domain.Principal { | |||
| 72 | 72 | // Only the owner creates a link: root, or a token with every scope and no | |
| 73 | 73 | // repository list. | |
| 74 | 74 | func TestLoginLinkIsOwnerOnly(t *testing.T) { | |
| 75 | + | t.Parallel() | |
| 75 | 76 | ctx := context.Background() | |
| 76 | 77 | f := newSessionFixture(t) | |
| 77 | 78 | partial := domain.TokenPrincipal(domain.Token{ID: "p", Scopes: domain.AllScopes()[:8]}) | |
| @@ -113,6 +114,7 @@ func mustRepo(t *testing.T, s string) domain.RepoName { | |||
| 113 | 114 | // AC1: checking a link does not use it; logging in uses it once; an expired or | |
| 114 | 115 | // replayed link fails. | |
| 115 | 116 | func TestLoginLinkLifecycle(t *testing.T) { | |
| 117 | + | t.Parallel() | |
| 116 | 118 | ctx := context.Background() | |
| 117 | 119 | f := newSessionFixture(t) | |
| 118 | 120 | ||
| @@ -155,6 +157,7 @@ func TestLoginLinkLifecycle(t *testing.T) { | |||
| 155 | 157 | // GO-003a: the session of a link made with a token never outlives that token; | |
| 156 | 158 | // root and a token that never expires leave it at SessionTTL. | |
| 157 | 159 | func TestSessionIsCappedByTheTokenThatMadeTheLink(t *testing.T) { | |
| 160 | + | t.Parallel() | |
| 158 | 161 | ctx := context.Background() | |
| 159 | 162 | f := newSessionFixture(t) | |
| 160 | 163 | tokenExpiring := func(in time.Duration) domain.Principal { | |
| @@ -210,6 +213,7 @@ func TestSessionIsCappedByTheTokenThatMadeTheLink(t *testing.T) { | |||
| 210 | 213 | ||
| 211 | 214 | // A single use also holds under concurrent confirmations. | |
| 212 | 215 | func TestLoginLinkConsumedOnceConcurrently(t *testing.T) { | |
| 216 | + | t.Parallel() | |
| 213 | 217 | f := newSessionFixture(t) | |
| 214 | 218 | nonce := f.nonce(t) | |
| 215 | 219 | var wg sync.WaitGroup | |
| @@ -231,6 +235,7 @@ func TestLoginLinkConsumedOnceConcurrently(t *testing.T) { | |||
| 231 | 235 | } | |
| 232 | 236 | ||
| 233 | 237 | func TestLoginLinksAreBounded(t *testing.T) { | |
| 238 | + | t.Parallel() | |
| 234 | 239 | ctx := context.Background() | |
| 235 | 240 | f := newSessionFixture(t) | |
| 236 | 241 | for range app.MaxPendingLoginLinks { | |
| @@ -259,6 +264,7 @@ type claims struct { | |||
| 259 | 264 | // AC1: a valid session is the owner; none, alg none, another algorithm, another | |
| 260 | 265 | // key, an old epoch, expiry, a missing claim and garbage are rejected. | |
| 261 | 266 | func TestSessionTokenVerification(t *testing.T) { | |
| 267 | + | t.Parallel() | |
| 262 | 268 | ctx := context.Background() | |
| 263 | 269 | f := newSessionFixture(t) | |
| 264 | 270 | now := f.clock.Now() | |
| @@ -308,6 +314,7 @@ func TestSessionTokenVerification(t *testing.T) { | |||
| 308 | 314 | } | |
| 309 | 315 | ||
| 310 | 316 | func TestSignOutEverywhereEndsSessions(t *testing.T) { | |
| 317 | + | t.Parallel() | |
| 311 | 318 | ctx := context.Background() | |
| 312 | 319 | f := newSessionFixture(t) | |
| 313 | 320 | a, _, err := f.sessions.Login(ctx, f.nonce(t)) | |
| @@ -331,6 +338,7 @@ func TestSignOutEverywhereEndsSessions(t *testing.T) { | |||
| 331 | 338 | // The owner signed in on the web signs what it writes as "owner", where root | |
| 332 | 339 | // signs as "root" although both hold every right. | |
| 333 | 340 | func TestAuthorOfOwnerSession(t *testing.T) { | |
| 341 | + | t.Parallel() | |
| 334 | 342 | assert.Equal(t, "owner", app.AuthorOf(domain.Owner())) | |
| 335 | 343 | assert.Equal(t, "root", app.AuthorOf(domain.Root())) | |
| 336 | 344 | assert.Equal(t, "ci", app.AuthorOf(domain.TokenPrincipal(domain.Token{Label: "ci", Scopes: domain.AllScopes()}))) | |
| @@ -72,6 +72,7 @@ func allScopeToken(repos ...domain.RepoName) domain.Principal { | |||||
| 72 | // Only the owner creates a link: root, or a token with every scope and no | 72 | // Only the owner creates a link: root, or a token with every scope and no | ||
| 73 | // repository list. | 73 | // repository list. | ||
| 74 | func TestLoginLinkIsOwnerOnly(t *testing.T) { | 74 | func TestLoginLinkIsOwnerOnly(t *testing.T) { | ||
| 75 | + | t.Parallel() | |||
| 75 | ctx := context.Background() | 76 | ctx := context.Background() | ||
| 76 | f := newSessionFixture(t) | 77 | f := newSessionFixture(t) | ||
| 77 | partial := domain.TokenPrincipal(domain.Token{ID: "p", Scopes: domain.AllScopes()[:8]}) | 78 | partial := domain.TokenPrincipal(domain.Token{ID: "p", Scopes: domain.AllScopes()[:8]}) | ||
| @@ -113,6 +114,7 @@ func mustRepo(t *testing.T, s string) domain.RepoName { | |||||
| 113 | // AC1: checking a link does not use it; logging in uses it once; an expired or | 114 | // AC1: checking a link does not use it; logging in uses it once; an expired or | ||
| 114 | // replayed link fails. | 115 | // replayed link fails. | ||
| 115 | func TestLoginLinkLifecycle(t *testing.T) { | 116 | func TestLoginLinkLifecycle(t *testing.T) { | ||
| 117 | + | t.Parallel() | |||
| 116 | ctx := context.Background() | 118 | ctx := context.Background() | ||
| 117 | f := newSessionFixture(t) | 119 | f := newSessionFixture(t) | ||
| 118 | 120 | ||||
| @@ -155,6 +157,7 @@ func TestLoginLinkLifecycle(t *testing.T) { | |||||
| 155 | // GO-003a: the session of a link made with a token never outlives that token; | 157 | // GO-003a: the session of a link made with a token never outlives that token; | ||
| 156 | // root and a token that never expires leave it at SessionTTL. | 158 | // root and a token that never expires leave it at SessionTTL. | ||
| 157 | func TestSessionIsCappedByTheTokenThatMadeTheLink(t *testing.T) { | 159 | func TestSessionIsCappedByTheTokenThatMadeTheLink(t *testing.T) { | ||
| 160 | + | t.Parallel() | |||
| 158 | ctx := context.Background() | 161 | ctx := context.Background() | ||
| 159 | f := newSessionFixture(t) | 162 | f := newSessionFixture(t) | ||
| 160 | tokenExpiring := func(in time.Duration) domain.Principal { | 163 | tokenExpiring := func(in time.Duration) domain.Principal { | ||
| @@ -210,6 +213,7 @@ func TestSessionIsCappedByTheTokenThatMadeTheLink(t *testing.T) { | |||||
| 210 | 213 | ||||
| 211 | // A single use also holds under concurrent confirmations. | 214 | // A single use also holds under concurrent confirmations. | ||
| 212 | func TestLoginLinkConsumedOnceConcurrently(t *testing.T) { | 215 | func TestLoginLinkConsumedOnceConcurrently(t *testing.T) { | ||
| 216 | + | t.Parallel() | |||
| 213 | f := newSessionFixture(t) | 217 | f := newSessionFixture(t) | ||
| 214 | nonce := f.nonce(t) | 218 | nonce := f.nonce(t) | ||
| 215 | var wg sync.WaitGroup | 219 | var wg sync.WaitGroup | ||
| @@ -231,6 +235,7 @@ func TestLoginLinkConsumedOnceConcurrently(t *testing.T) { | |||||
| 231 | } | 235 | } | ||
| 232 | 236 | ||||
| 233 | func TestLoginLinksAreBounded(t *testing.T) { | 237 | func TestLoginLinksAreBounded(t *testing.T) { | ||
| 238 | + | t.Parallel() | |||
| 234 | ctx := context.Background() | 239 | ctx := context.Background() | ||
| 235 | f := newSessionFixture(t) | 240 | f := newSessionFixture(t) | ||
| 236 | for range app.MaxPendingLoginLinks { | 241 | for range app.MaxPendingLoginLinks { | ||
| @@ -259,6 +264,7 @@ type claims struct { | |||||
| 259 | // AC1: a valid session is the owner; none, alg none, another algorithm, another | 264 | // AC1: a valid session is the owner; none, alg none, another algorithm, another | ||
| 260 | // key, an old epoch, expiry, a missing claim and garbage are rejected. | 265 | // key, an old epoch, expiry, a missing claim and garbage are rejected. | ||
| 261 | func TestSessionTokenVerification(t *testing.T) { | 266 | func TestSessionTokenVerification(t *testing.T) { | ||
| 267 | + | t.Parallel() | |||
| 262 | ctx := context.Background() | 268 | ctx := context.Background() | ||
| 263 | f := newSessionFixture(t) | 269 | f := newSessionFixture(t) | ||
| 264 | now := f.clock.Now() | 270 | now := f.clock.Now() | ||
| @@ -308,6 +314,7 @@ func TestSessionTokenVerification(t *testing.T) { | |||||
| 308 | } | 314 | } | ||
| 309 | 315 | ||||
| 310 | func TestSignOutEverywhereEndsSessions(t *testing.T) { | 316 | func TestSignOutEverywhereEndsSessions(t *testing.T) { | ||
| 317 | + | t.Parallel() | |||
| 311 | ctx := context.Background() | 318 | ctx := context.Background() | ||
| 312 | f := newSessionFixture(t) | 319 | f := newSessionFixture(t) | ||
| 313 | a, _, err := f.sessions.Login(ctx, f.nonce(t)) | 320 | a, _, err := f.sessions.Login(ctx, f.nonce(t)) | ||
| @@ -331,6 +338,7 @@ func TestSignOutEverywhereEndsSessions(t *testing.T) { | |||||
| 331 | // The owner signed in on the web signs what it writes as "owner", where root | 338 | // The owner signed in on the web signs what it writes as "owner", where root | ||
| 332 | // signs as "root" although both hold every right. | 339 | // signs as "root" although both hold every right. | ||
| 333 | func TestAuthorOfOwnerSession(t *testing.T) { | 340 | func TestAuthorOfOwnerSession(t *testing.T) { | ||
| 341 | + | t.Parallel() | |||
| 334 | assert.Equal(t, "owner", app.AuthorOf(domain.Owner())) | 342 | assert.Equal(t, "owner", app.AuthorOf(domain.Owner())) | ||
| 335 | assert.Equal(t, "root", app.AuthorOf(domain.Root())) | 343 | assert.Equal(t, "root", app.AuthorOf(domain.Root())) | ||
| 336 | assert.Equal(t, "ci", app.AuthorOf(domain.TokenPrincipal(domain.Token{Label: "ci", Scopes: domain.AllScopes()}))) | 344 | assert.Equal(t, "ci", app.AuthorOf(domain.TokenPrincipal(domain.Token{Label: "ci", Scopes: domain.AllScopes()}))) | ||
M internal/app/tokens_test.go +39 -0
| @@ -51,6 +51,7 @@ func names(t *testing.T, ss ...string) []domain.RepoName { | |||
| 51 | 51 | ||
| 52 | 52 | // Labels that only contain a reserved word are fine. | |
| 53 | 53 | func TestTokensCreateAllowsLabelsNearReserved(t *testing.T) { | |
| 54 | + | t.Parallel() | |
| 54 | 55 | ctx := context.Background() | |
| 55 | 56 | tests := []struct{ name, label string }{ | |
| 56 | 57 | {"digit suffix", "owner2"}, | |
| @@ -61,6 +62,7 @@ func TestTokensCreateAllowsLabelsNearReserved(t *testing.T) { | |||
| 61 | 62 | } | |
| 62 | 63 | for _, tt := range tests { | |
| 63 | 64 | t.Run(tt.name, func(t *testing.T) { | |
| 65 | + | t.Parallel() | |
| 64 | 66 | f := newTokenFixture(t) | |
| 65 | 67 | f.store.EXPECT().Create(ctx, mock.Anything).Return(nil) | |
| 66 | 68 | _, err := f.tokens.Create(ctx, domain.Root(), app.CreateTokenParams{Label: tt.label, Scopes: scopes(domain.ScopeRepoRead)}) | |
| @@ -70,9 +72,11 @@ func TestTokensCreateAllowsLabelsNearReserved(t *testing.T) { | |||
| 70 | 72 | } | |
| 71 | 73 | ||
| 72 | 74 | func TestTokensCreate(t *testing.T) { | |
| 75 | + | t.Parallel() | |
| 73 | 76 | ctx := context.Background() | |
| 74 | 77 | ||
| 75 | 78 | t.Run("root creates a token with a well-formed secret", func(t *testing.T) { | |
| 79 | + | t.Parallel() | |
| 76 | 80 | f := newTokenFixture(t) | |
| 77 | 81 | var stored domain.Token | |
| 78 | 82 | f.store.EXPECT().Create(ctx, mock.Anything).Run(func(_ context.Context, tok domain.Token) { stored = tok }).Return(nil) | |
| @@ -102,6 +106,7 @@ func TestTokensCreate(t *testing.T) { | |||
| 102 | 106 | }) | |
| 103 | 107 | ||
| 104 | 108 | t.Run("secrets are unique", func(t *testing.T) { | |
| 109 | + | t.Parallel() | |
| 105 | 110 | f := newTokenFixture(t) | |
| 106 | 111 | f.store.EXPECT().Create(ctx, mock.Anything).Return(nil) | |
| 107 | 112 | a, err := f.tokens.Create(ctx, domain.Root(), app.CreateTokenParams{Label: "a", Scopes: scopes(domain.ScopeRepoRead)}) | |
| @@ -113,6 +118,7 @@ func TestTokensCreate(t *testing.T) { | |||
| 113 | 118 | }) | |
| 114 | 119 | ||
| 115 | 120 | t.Run("retries on a collision", func(t *testing.T) { | |
| 121 | + | t.Parallel() | |
| 116 | 122 | f := newTokenFixture(t) | |
| 117 | 123 | f.store.EXPECT().Create(ctx, mock.Anything).Return(domain.ErrAlreadyExists).Once() | |
| 118 | 124 | f.store.EXPECT().Create(ctx, mock.Anything).Return(nil).Once() | |
| @@ -121,6 +127,7 @@ func TestTokensCreate(t *testing.T) { | |||
| 121 | 127 | }) | |
| 122 | 128 | ||
| 123 | 129 | t.Run("store failure", func(t *testing.T) { | |
| 130 | + | t.Parallel() | |
| 124 | 131 | f := newTokenFixture(t) | |
| 125 | 132 | boom := errors.New("boom") | |
| 126 | 133 | f.store.EXPECT().Create(ctx, mock.Anything).Return(boom) | |
| @@ -148,6 +155,7 @@ func TestTokensCreate(t *testing.T) { | |||
| 148 | 155 | } | |
| 149 | 156 | for _, tt := range invalid { | |
| 150 | 157 | t.Run("invalid/"+tt.name, func(t *testing.T) { | |
| 158 | + | t.Parallel() | |
| 151 | 159 | f := newTokenFixture(t) | |
| 152 | 160 | _, err := f.tokens.Create(ctx, domain.Root(), tt.params) | |
| 153 | 161 | assert.ErrorIs(t, err, tt.want) | |
| @@ -156,6 +164,7 @@ func TestTokensCreate(t *testing.T) { | |||
| 156 | 164 | } | |
| 157 | 165 | ||
| 158 | 166 | func TestTokensCreateNoEscalation(t *testing.T) { | |
| 167 | + | t.Parallel() | |
| 159 | 168 | ctx := context.Background() | |
| 160 | 169 | exp := now.Add(time.Hour) | |
| 161 | 170 | admin := func(repos []domain.RepoName, expires time.Time, extra ...domain.Scope) domain.Principal { | |
| @@ -204,6 +213,7 @@ func TestTokensCreateNoEscalation(t *testing.T) { | |||
| 204 | 213 | } | |
| 205 | 214 | for _, tt := range tests { | |
| 206 | 215 | t.Run(tt.name, func(t *testing.T) { | |
| 216 | + | t.Parallel() | |
| 207 | 217 | f := newTokenFixture(t) | |
| 208 | 218 | if tt.want == nil { | |
| 209 | 219 | f.store.EXPECT().Create(ctx, mock.Anything).Return(nil) | |
| @@ -221,11 +231,13 @@ func TestTokensCreateNoEscalation(t *testing.T) { | |||
| 221 | 231 | } | |
| 222 | 232 | ||
| 223 | 233 | func TestTokensListRevoke(t *testing.T) { | |
| 234 | + | t.Parallel() | |
| 224 | 235 | ctx := context.Background() | |
| 225 | 236 | tokenAdmin := domain.TokenPrincipal(domain.Token{ID: "adm", Scopes: scopes(domain.ScopeTokenAdmin)}) | |
| 226 | 237 | writer := domain.TokenPrincipal(domain.Token{Scopes: scopes(domain.ScopeRepoWrite)}) | |
| 227 | 238 | ||
| 228 | 239 | t.Run("list for the admin token and root", func(t *testing.T) { | |
| 240 | + | t.Parallel() | |
| 229 | 241 | f := newTokenFixture(t) | |
| 230 | 242 | want := []domain.Token{{ID: "a", Scopes: scopes(domain.ScopeTokenAdmin)}} | |
| 231 | 243 | f.store.EXPECT().List(ctx).Return(want, nil).Twice() | |
| @@ -241,6 +253,7 @@ func TestTokensListRevoke(t *testing.T) { | |||
| 241 | 253 | }) | |
| 242 | 254 | ||
| 243 | 255 | t.Run("revoke", func(t *testing.T) { | |
| 256 | + | t.Parallel() | |
| 244 | 257 | f := newTokenFixture(t) | |
| 245 | 258 | f.store.EXPECT().Get(ctx, "a").Return(domain.Token{ID: "a", Scopes: scopes(domain.ScopeTokenAdmin)}, nil).Twice() | |
| 246 | 259 | f.store.EXPECT().Get(ctx, "zz").Return(domain.Token{}, domain.ErrNotFound).Once() | |
| @@ -256,6 +269,7 @@ func TestTokensListRevoke(t *testing.T) { | |||
| 256 | 269 | // GO-003b: revoking a token with owner rights signs every browser out (the | |
| 257 | 270 | // session epoch is bumped); revoking any other token does not. | |
| 258 | 271 | func TestTokensRevokeOwnerRightsEndsWebSessions(t *testing.T) { | |
| 272 | + | t.Parallel() | |
| 259 | 273 | ctx := context.Background() | |
| 260 | 274 | ownerTok := domain.Token{ID: "own", Scopes: domain.AllScopes()} | |
| 261 | 275 | ||
| @@ -270,6 +284,7 @@ func TestTokensRevokeOwnerRightsEndsWebSessions(t *testing.T) { | |||
| 270 | 284 | "repo list": {domain.Token{ID: "l", Scopes: domain.AllScopes(), Repos: names(t, "a")}, domain.Root(), false}, | |
| 271 | 285 | } { | |
| 272 | 286 | t.Run(name, func(t *testing.T) { | |
| 287 | + | t.Parallel() | |
| 273 | 288 | f := newTokenFixture(t) | |
| 274 | 289 | f.store.EXPECT().Get(ctx, tc.tok.ID).Return(tc.tok, nil) | |
| 275 | 290 | f.store.EXPECT().Revoke(ctx, tc.tok.ID).Return(nil) | |
| @@ -281,6 +296,7 @@ func TestTokensRevokeOwnerRightsEndsWebSessions(t *testing.T) { | |||
| 281 | 296 | } | |
| 282 | 297 | ||
| 283 | 298 | t.Run("a revoke that fails bumps nothing", func(t *testing.T) { | |
| 299 | + | t.Parallel() | |
| 284 | 300 | f := newTokenFixture(t) | |
| 285 | 301 | f.store.EXPECT().Get(ctx, "own").Return(ownerTok, nil) | |
| 286 | 302 | f.store.EXPECT().Revoke(ctx, "own").Return(errors.New("kv down")) | |
| @@ -288,6 +304,7 @@ func TestTokensRevokeOwnerRightsEndsWebSessions(t *testing.T) { | |||
| 288 | 304 | }) | |
| 289 | 305 | ||
| 290 | 306 | t.Run("a token out of reach is not revoked and bumps nothing", func(t *testing.T) { | |
| 307 | + | t.Parallel() | |
| 291 | 308 | f := newTokenFixture(t) | |
| 292 | 309 | limited := domain.TokenPrincipal(domain.Token{ID: "adm", Scopes: scopes(domain.ScopeTokenAdmin)}) | |
| 293 | 310 | f.store.EXPECT().Get(ctx, "own").Return(ownerTok, nil) | |
| @@ -295,6 +312,7 @@ func TestTokensRevokeOwnerRightsEndsWebSessions(t *testing.T) { | |||
| 295 | 312 | }) | |
| 296 | 313 | ||
| 297 | 314 | t.Run("a failure to sign out is reported once the token is revoked", func(t *testing.T) { | |
| 315 | + | t.Parallel() | |
| 298 | 316 | f := newTokenFixture(t) | |
| 299 | 317 | f.store.EXPECT().Get(ctx, "own").Return(ownerTok, nil) | |
| 300 | 318 | f.store.EXPECT().Revoke(ctx, "own").Return(nil) | |
| @@ -306,6 +324,7 @@ func TestTokensRevokeOwnerRightsEndsWebSessions(t *testing.T) { | |||
| 306 | 324 | // A token admin sees and revokes only the tokens it could have created: the | |
| 307 | 325 | // no-escalation rule of Create, applied to scopes, repositories and expiry. | |
| 308 | 326 | func TestTokensAdminReach(t *testing.T) { | |
| 327 | + | t.Parallel() | |
| 309 | 328 | ctx := context.Background() | |
| 310 | 329 | exp := now.Add(48 * time.Hour) | |
| 311 | 330 | admin := func(repos []domain.RepoName, expires time.Time, extra ...domain.Scope) domain.Principal { | |
| @@ -343,6 +362,7 @@ func TestTokensAdminReach(t *testing.T) { | |||
| 343 | 362 | } | |
| 344 | 363 | for _, tt := range tests { | |
| 345 | 364 | t.Run("list: "+tt.name, func(t *testing.T) { | |
| 365 | + | t.Parallel() | |
| 346 | 366 | f := newTokenFixture(t) | |
| 347 | 367 | other := tok("other", scopes(domain.ScopeRepoRead), nil, time.Time{}) | |
| 348 | 368 | f.store.EXPECT().List(ctx).Return([]domain.Token{tt.target, other}, nil) | |
| @@ -356,6 +376,7 @@ func TestTokensAdminReach(t *testing.T) { | |||
| 356 | 376 | assert.Contains(t, got, tt.target) | |
| 357 | 377 | }) | |
| 358 | 378 | t.Run("revoke: "+tt.name, func(t *testing.T) { | |
| 379 | + | t.Parallel() | |
| 359 | 380 | f := newTokenFixture(t) | |
| 360 | 381 | f.store.EXPECT().Get(ctx, "t").Return(tt.target, nil) | |
| 361 | 382 | if tt.within { | |
| @@ -369,6 +390,7 @@ func TestTokensAdminReach(t *testing.T) { | |||
| 369 | 390 | } | |
| 370 | 391 | ||
| 371 | 392 | t.Run("root lists and revokes everything", func(t *testing.T) { | |
| 393 | + | t.Parallel() | |
| 372 | 394 | f := newTokenFixture(t) | |
| 373 | 395 | all := []domain.Token{tok("x", domain.AllScopes(), nil, time.Time{}), tok("y", scopes(domain.ScopeMRMerge), a, exp)} | |
| 374 | 396 | f.store.EXPECT().List(ctx).Return(all, nil) | |
| @@ -382,12 +404,14 @@ func TestTokensAdminReach(t *testing.T) { | |||
| 382 | 404 | }) | |
| 383 | 405 | ||
| 384 | 406 | t.Run("an unknown id is not found for everyone", func(t *testing.T) { | |
| 407 | + | t.Parallel() | |
| 385 | 408 | f := newTokenFixture(t) | |
| 386 | 409 | f.store.EXPECT().Get(ctx, "nope").Return(domain.Token{}, domain.ErrNotFound) | |
| 387 | 410 | assert.ErrorIs(t, f.tokens.Revoke(ctx, admin(nil, time.Time{}), "nope"), domain.ErrNotFound) | |
| 388 | 411 | }) | |
| 389 | 412 | ||
| 390 | 413 | t.Run("a store failure on the lookup is returned", func(t *testing.T) { | |
| 414 | + | t.Parallel() | |
| 391 | 415 | f := newTokenFixture(t) | |
| 392 | 416 | boom := errors.New("boom") | |
| 393 | 417 | f.store.EXPECT().Get(ctx, "t").Return(domain.Token{}, boom) | |
| @@ -396,33 +420,40 @@ func TestTokensAdminReach(t *testing.T) { | |||
| 396 | 420 | } | |
| 397 | 421 | ||
| 398 | 422 | func TestAuthRevalidate(t *testing.T) { | |
| 423 | + | t.Parallel() | |
| 399 | 424 | ctx := context.Background() | |
| 400 | 425 | p := domain.TokenPrincipal(domain.Token{ID: "t1", Scopes: scopes(domain.ScopeRepoWrite)}) | |
| 401 | 426 | ||
| 402 | 427 | t.Run("root is always valid", func(t *testing.T) { | |
| 428 | + | t.Parallel() | |
| 403 | 429 | f := newTokenFixture(t) | |
| 404 | 430 | require.NoError(t, f.auth.Revalidate(ctx, domain.Root())) | |
| 405 | 431 | }) | |
| 406 | 432 | t.Run("anonymous is not", func(t *testing.T) { | |
| 433 | + | t.Parallel() | |
| 407 | 434 | f := newTokenFixture(t) | |
| 408 | 435 | assert.ErrorIs(t, f.auth.Revalidate(ctx, domain.Anonymous()), domain.ErrUnauthenticated) | |
| 409 | 436 | }) | |
| 410 | 437 | t.Run("a stored token that exists", func(t *testing.T) { | |
| 438 | + | t.Parallel() | |
| 411 | 439 | f := newTokenFixture(t) | |
| 412 | 440 | f.store.EXPECT().Get(ctx, "t1").Return(domain.Token{ID: "t1", ExpiresAt: now.Add(time.Hour)}, nil) | |
| 413 | 441 | require.NoError(t, f.auth.Revalidate(ctx, p)) | |
| 414 | 442 | }) | |
| 415 | 443 | t.Run("revoked", func(t *testing.T) { | |
| 444 | + | t.Parallel() | |
| 416 | 445 | f := newTokenFixture(t) | |
| 417 | 446 | f.store.EXPECT().Get(ctx, "t1").Return(domain.Token{}, domain.ErrNotFound) | |
| 418 | 447 | assert.ErrorIs(t, f.auth.Revalidate(ctx, p), domain.ErrUnauthenticated) | |
| 419 | 448 | }) | |
| 420 | 449 | t.Run("expired", func(t *testing.T) { | |
| 450 | + | t.Parallel() | |
| 421 | 451 | f := newTokenFixture(t) | |
| 422 | 452 | f.store.EXPECT().Get(ctx, "t1").Return(domain.Token{ID: "t1", ExpiresAt: now}, nil) | |
| 423 | 453 | assert.ErrorIs(t, f.auth.Revalidate(ctx, p), domain.ErrUnauthenticated) | |
| 424 | 454 | }) | |
| 425 | 455 | t.Run("a store failure is an error, not a pass", func(t *testing.T) { | |
| 456 | + | t.Parallel() | |
| 426 | 457 | f := newTokenFixture(t) | |
| 427 | 458 | boom := errors.New("boom") | |
| 428 | 459 | f.store.EXPECT().Get(ctx, "t1").Return(domain.Token{}, boom) | |
| @@ -433,6 +464,7 @@ func TestAuthRevalidate(t *testing.T) { | |||
| 433 | 464 | } | |
| 434 | 465 | ||
| 435 | 466 | func TestAuthenticate(t *testing.T) { | |
| 467 | + | t.Parallel() | |
| 436 | 468 | ctx := context.Background() | |
| 437 | 469 | secret := "ermite_stored-secret" | |
| 438 | 470 | hash := domain.HashTokenSecret(secret) | |
| @@ -442,6 +474,7 @@ func TestAuthenticate(t *testing.T) { | |||
| 442 | 474 | } | |
| 443 | 475 | ||
| 444 | 476 | t.Run("root", func(t *testing.T) { | |
| 477 | + | t.Parallel() | |
| 445 | 478 | f := newTokenFixture(t) | |
| 446 | 479 | p, err := f.auth.Authenticate(ctx, rootSecret) | |
| 447 | 480 | require.NoError(t, err) | |
| @@ -449,6 +482,7 @@ func TestAuthenticate(t *testing.T) { | |||
| 449 | 482 | }) | |
| 450 | 483 | ||
| 451 | 484 | t.Run("empty and unknown secrets", func(t *testing.T) { | |
| 485 | + | t.Parallel() | |
| 452 | 486 | f := newTokenFixture(t) | |
| 453 | 487 | _, err := f.auth.Authenticate(ctx, "") | |
| 454 | 488 | assert.ErrorIs(t, err, domain.ErrUnauthenticated) | |
| @@ -458,6 +492,7 @@ func TestAuthenticate(t *testing.T) { | |||
| 458 | 492 | }) | |
| 459 | 493 | ||
| 460 | 494 | t.Run("stored token first use touches", func(t *testing.T) { | |
| 495 | + | t.Parallel() | |
| 461 | 496 | f := newTokenFixture(t) | |
| 462 | 497 | f.store.EXPECT().FindByHash(ctx, hash).Return(tok, nil) | |
| 463 | 498 | f.store.EXPECT().Touch(ctx, "t1", now).Return(nil) | |
| @@ -469,6 +504,7 @@ func TestAuthenticate(t *testing.T) { | |||
| 469 | 504 | }) | |
| 470 | 505 | ||
| 471 | 506 | t.Run("touch at most once a minute", func(t *testing.T) { | |
| 507 | + | t.Parallel() | |
| 472 | 508 | f := newTokenFixture(t) | |
| 473 | 509 | recent := tok | |
| 474 | 510 | recent.LastUsedAt = now.Add(-30 * time.Second) | |
| @@ -486,6 +522,7 @@ func TestAuthenticate(t *testing.T) { | |||
| 486 | 522 | }) | |
| 487 | 523 | ||
| 488 | 524 | t.Run("touch failure does not fail authentication", func(t *testing.T) { | |
| 525 | + | t.Parallel() | |
| 489 | 526 | f := newTokenFixture(t) | |
| 490 | 527 | f.store.EXPECT().FindByHash(ctx, hash).Return(tok, nil) | |
| 491 | 528 | f.store.EXPECT().Touch(ctx, "t1", now).Return(errors.New("disk")) | |
| @@ -494,6 +531,7 @@ func TestAuthenticate(t *testing.T) { | |||
| 494 | 531 | }) | |
| 495 | 532 | ||
| 496 | 533 | t.Run("expired", func(t *testing.T) { | |
| 534 | + | t.Parallel() | |
| 497 | 535 | f := newTokenFixture(t) | |
| 498 | 536 | exp := tok | |
| 499 | 537 | exp.ExpiresAt = now | |
| @@ -503,6 +541,7 @@ func TestAuthenticate(t *testing.T) { | |||
| 503 | 541 | }) | |
| 504 | 542 | ||
| 505 | 543 | t.Run("store error is not an authentication failure", func(t *testing.T) { | |
| 544 | + | t.Parallel() | |
| 506 | 545 | f := newTokenFixture(t) | |
| 507 | 546 | boom := errors.New("boom") | |
| 508 | 547 | f.store.EXPECT().FindByHash(ctx, hash).Return(domain.Token{}, boom) | |
| @@ -51,6 +51,7 @@ func names(t *testing.T, ss ...string) []domain.RepoName { | |||||
| 51 | 51 | ||||
| 52 | // Labels that only contain a reserved word are fine. | 52 | // Labels that only contain a reserved word are fine. | ||
| 53 | func TestTokensCreateAllowsLabelsNearReserved(t *testing.T) { | 53 | func TestTokensCreateAllowsLabelsNearReserved(t *testing.T) { | ||
| 54 | + | t.Parallel() | |||
| 54 | ctx := context.Background() | 55 | ctx := context.Background() | ||
| 55 | tests := []struct{ name, label string }{ | 56 | tests := []struct{ name, label string }{ | ||
| 56 | {"digit suffix", "owner2"}, | 57 | {"digit suffix", "owner2"}, | ||
| @@ -61,6 +62,7 @@ func TestTokensCreateAllowsLabelsNearReserved(t *testing.T) { | |||||
| 61 | } | 62 | } | ||
| 62 | for _, tt := range tests { | 63 | for _, tt := range tests { | ||
| 63 | t.Run(tt.name, func(t *testing.T) { | 64 | t.Run(tt.name, func(t *testing.T) { | ||
| 65 | + | t.Parallel() | |||
| 64 | f := newTokenFixture(t) | 66 | f := newTokenFixture(t) | ||
| 65 | f.store.EXPECT().Create(ctx, mock.Anything).Return(nil) | 67 | f.store.EXPECT().Create(ctx, mock.Anything).Return(nil) | ||
| 66 | _, err := f.tokens.Create(ctx, domain.Root(), app.CreateTokenParams{Label: tt.label, Scopes: scopes(domain.ScopeRepoRead)}) | 68 | _, err := f.tokens.Create(ctx, domain.Root(), app.CreateTokenParams{Label: tt.label, Scopes: scopes(domain.ScopeRepoRead)}) | ||
| @@ -70,9 +72,11 @@ func TestTokensCreateAllowsLabelsNearReserved(t *testing.T) { | |||||
| 70 | } | 72 | } | ||
| 71 | 73 | ||||
| 72 | func TestTokensCreate(t *testing.T) { | 74 | func TestTokensCreate(t *testing.T) { | ||
| 75 | + | t.Parallel() | |||
| 73 | ctx := context.Background() | 76 | ctx := context.Background() | ||
| 74 | 77 | ||||
| 75 | t.Run("root creates a token with a well-formed secret", func(t *testing.T) { | 78 | t.Run("root creates a token with a well-formed secret", func(t *testing.T) { | ||
| 79 | + | t.Parallel() | |||
| 76 | f := newTokenFixture(t) | 80 | f := newTokenFixture(t) | ||
| 77 | var stored domain.Token | 81 | var stored domain.Token | ||
| 78 | f.store.EXPECT().Create(ctx, mock.Anything).Run(func(_ context.Context, tok domain.Token) { stored = tok }).Return(nil) | 82 | f.store.EXPECT().Create(ctx, mock.Anything).Run(func(_ context.Context, tok domain.Token) { stored = tok }).Return(nil) | ||
| @@ -102,6 +106,7 @@ func TestTokensCreate(t *testing.T) { | |||||
| 102 | }) | 106 | }) | ||
| 103 | 107 | ||||
| 104 | t.Run("secrets are unique", func(t *testing.T) { | 108 | t.Run("secrets are unique", func(t *testing.T) { | ||
| 109 | + | t.Parallel() | |||
| 105 | f := newTokenFixture(t) | 110 | f := newTokenFixture(t) | ||
| 106 | f.store.EXPECT().Create(ctx, mock.Anything).Return(nil) | 111 | f.store.EXPECT().Create(ctx, mock.Anything).Return(nil) | ||
| 107 | a, err := f.tokens.Create(ctx, domain.Root(), app.CreateTokenParams{Label: "a", Scopes: scopes(domain.ScopeRepoRead)}) | 112 | a, err := f.tokens.Create(ctx, domain.Root(), app.CreateTokenParams{Label: "a", Scopes: scopes(domain.ScopeRepoRead)}) | ||
| @@ -113,6 +118,7 @@ func TestTokensCreate(t *testing.T) { | |||||
| 113 | }) | 118 | }) | ||
| 114 | 119 | ||||
| 115 | t.Run("retries on a collision", func(t *testing.T) { | 120 | t.Run("retries on a collision", func(t *testing.T) { | ||
| 121 | + | t.Parallel() | |||
| 116 | f := newTokenFixture(t) | 122 | f := newTokenFixture(t) | ||
| 117 | f.store.EXPECT().Create(ctx, mock.Anything).Return(domain.ErrAlreadyExists).Once() | 123 | f.store.EXPECT().Create(ctx, mock.Anything).Return(domain.ErrAlreadyExists).Once() | ||
| 118 | f.store.EXPECT().Create(ctx, mock.Anything).Return(nil).Once() | 124 | f.store.EXPECT().Create(ctx, mock.Anything).Return(nil).Once() | ||
| @@ -121,6 +127,7 @@ func TestTokensCreate(t *testing.T) { | |||||
| 121 | }) | 127 | }) | ||
| 122 | 128 | ||||
| 123 | t.Run("store failure", func(t *testing.T) { | 129 | t.Run("store failure", func(t *testing.T) { | ||
| 130 | + | t.Parallel() | |||
| 124 | f := newTokenFixture(t) | 131 | f := newTokenFixture(t) | ||
| 125 | boom := errors.New("boom") | 132 | boom := errors.New("boom") | ||
| 126 | f.store.EXPECT().Create(ctx, mock.Anything).Return(boom) | 133 | f.store.EXPECT().Create(ctx, mock.Anything).Return(boom) | ||
| @@ -148,6 +155,7 @@ func TestTokensCreate(t *testing.T) { | |||||
| 148 | } | 155 | } | ||
| 149 | for _, tt := range invalid { | 156 | for _, tt := range invalid { | ||
| 150 | t.Run("invalid/"+tt.name, func(t *testing.T) { | 157 | t.Run("invalid/"+tt.name, func(t *testing.T) { | ||
| 158 | + | t.Parallel() | |||
| 151 | f := newTokenFixture(t) | 159 | f := newTokenFixture(t) | ||
| 152 | _, err := f.tokens.Create(ctx, domain.Root(), tt.params) | 160 | _, err := f.tokens.Create(ctx, domain.Root(), tt.params) | ||
| 153 | assert.ErrorIs(t, err, tt.want) | 161 | assert.ErrorIs(t, err, tt.want) | ||
| @@ -156,6 +164,7 @@ func TestTokensCreate(t *testing.T) { | |||||
| 156 | } | 164 | } | ||
| 157 | 165 | ||||
| 158 | func TestTokensCreateNoEscalation(t *testing.T) { | 166 | func TestTokensCreateNoEscalation(t *testing.T) { | ||
| 167 | + | t.Parallel() | |||
| 159 | ctx := context.Background() | 168 | ctx := context.Background() | ||
| 160 | exp := now.Add(time.Hour) | 169 | exp := now.Add(time.Hour) | ||
| 161 | admin := func(repos []domain.RepoName, expires time.Time, extra ...domain.Scope) domain.Principal { | 170 | admin := func(repos []domain.RepoName, expires time.Time, extra ...domain.Scope) domain.Principal { | ||
| @@ -204,6 +213,7 @@ func TestTokensCreateNoEscalation(t *testing.T) { | |||||
| 204 | } | 213 | } | ||
| 205 | for _, tt := range tests { | 214 | for _, tt := range tests { | ||
| 206 | t.Run(tt.name, func(t *testing.T) { | 215 | t.Run(tt.name, func(t *testing.T) { | ||
| 216 | + | t.Parallel() | |||
| 207 | f := newTokenFixture(t) | 217 | f := newTokenFixture(t) | ||
| 208 | if tt.want == nil { | 218 | if tt.want == nil { | ||
| 209 | f.store.EXPECT().Create(ctx, mock.Anything).Return(nil) | 219 | f.store.EXPECT().Create(ctx, mock.Anything).Return(nil) | ||
| @@ -221,11 +231,13 @@ func TestTokensCreateNoEscalation(t *testing.T) { | |||||
| 221 | } | 231 | } | ||
| 222 | 232 | ||||
| 223 | func TestTokensListRevoke(t *testing.T) { | 233 | func TestTokensListRevoke(t *testing.T) { | ||
| 234 | + | t.Parallel() | |||
| 224 | ctx := context.Background() | 235 | ctx := context.Background() | ||
| 225 | tokenAdmin := domain.TokenPrincipal(domain.Token{ID: "adm", Scopes: scopes(domain.ScopeTokenAdmin)}) | 236 | tokenAdmin := domain.TokenPrincipal(domain.Token{ID: "adm", Scopes: scopes(domain.ScopeTokenAdmin)}) | ||
| 226 | writer := domain.TokenPrincipal(domain.Token{Scopes: scopes(domain.ScopeRepoWrite)}) | 237 | writer := domain.TokenPrincipal(domain.Token{Scopes: scopes(domain.ScopeRepoWrite)}) | ||
| 227 | 238 | ||||
| 228 | t.Run("list for the admin token and root", func(t *testing.T) { | 239 | t.Run("list for the admin token and root", func(t *testing.T) { | ||
| 240 | + | t.Parallel() | |||
| 229 | f := newTokenFixture(t) | 241 | f := newTokenFixture(t) | ||
| 230 | want := []domain.Token{{ID: "a", Scopes: scopes(domain.ScopeTokenAdmin)}} | 242 | want := []domain.Token{{ID: "a", Scopes: scopes(domain.ScopeTokenAdmin)}} | ||
| 231 | f.store.EXPECT().List(ctx).Return(want, nil).Twice() | 243 | f.store.EXPECT().List(ctx).Return(want, nil).Twice() | ||
| @@ -241,6 +253,7 @@ func TestTokensListRevoke(t *testing.T) { | |||||
| 241 | }) | 253 | }) | ||
| 242 | 254 | ||||
| 243 | t.Run("revoke", func(t *testing.T) { | 255 | t.Run("revoke", func(t *testing.T) { | ||
| 256 | + | t.Parallel() | |||
| 244 | f := newTokenFixture(t) | 257 | f := newTokenFixture(t) | ||
| 245 | f.store.EXPECT().Get(ctx, "a").Return(domain.Token{ID: "a", Scopes: scopes(domain.ScopeTokenAdmin)}, nil).Twice() | 258 | f.store.EXPECT().Get(ctx, "a").Return(domain.Token{ID: "a", Scopes: scopes(domain.ScopeTokenAdmin)}, nil).Twice() | ||
| 246 | f.store.EXPECT().Get(ctx, "zz").Return(domain.Token{}, domain.ErrNotFound).Once() | 259 | f.store.EXPECT().Get(ctx, "zz").Return(domain.Token{}, domain.ErrNotFound).Once() | ||
| @@ -256,6 +269,7 @@ func TestTokensListRevoke(t *testing.T) { | |||||
| 256 | // GO-003b: revoking a token with owner rights signs every browser out (the | 269 | // GO-003b: revoking a token with owner rights signs every browser out (the | ||
| 257 | // session epoch is bumped); revoking any other token does not. | 270 | // session epoch is bumped); revoking any other token does not. | ||
| 258 | func TestTokensRevokeOwnerRightsEndsWebSessions(t *testing.T) { | 271 | func TestTokensRevokeOwnerRightsEndsWebSessions(t *testing.T) { | ||
| 272 | + | t.Parallel() | |||
| 259 | ctx := context.Background() | 273 | ctx := context.Background() | ||
| 260 | ownerTok := domain.Token{ID: "own", Scopes: domain.AllScopes()} | 274 | ownerTok := domain.Token{ID: "own", Scopes: domain.AllScopes()} | ||
| 261 | 275 | ||||
| @@ -270,6 +284,7 @@ func TestTokensRevokeOwnerRightsEndsWebSessions(t *testing.T) { | |||||
| 270 | "repo list": {domain.Token{ID: "l", Scopes: domain.AllScopes(), Repos: names(t, "a")}, domain.Root(), false}, | 284 | "repo list": {domain.Token{ID: "l", Scopes: domain.AllScopes(), Repos: names(t, "a")}, domain.Root(), false}, | ||
| 271 | } { | 285 | } { | ||
| 272 | t.Run(name, func(t *testing.T) { | 286 | t.Run(name, func(t *testing.T) { | ||
| 287 | + | t.Parallel() | |||
| 273 | f := newTokenFixture(t) | 288 | f := newTokenFixture(t) | ||
| 274 | f.store.EXPECT().Get(ctx, tc.tok.ID).Return(tc.tok, nil) | 289 | f.store.EXPECT().Get(ctx, tc.tok.ID).Return(tc.tok, nil) | ||
| 275 | f.store.EXPECT().Revoke(ctx, tc.tok.ID).Return(nil) | 290 | f.store.EXPECT().Revoke(ctx, tc.tok.ID).Return(nil) | ||
| @@ -281,6 +296,7 @@ func TestTokensRevokeOwnerRightsEndsWebSessions(t *testing.T) { | |||||
| 281 | } | 296 | } | ||
| 282 | 297 | ||||
| 283 | t.Run("a revoke that fails bumps nothing", func(t *testing.T) { | 298 | t.Run("a revoke that fails bumps nothing", func(t *testing.T) { | ||
| 299 | + | t.Parallel() | |||
| 284 | f := newTokenFixture(t) | 300 | f := newTokenFixture(t) | ||
| 285 | f.store.EXPECT().Get(ctx, "own").Return(ownerTok, nil) | 301 | f.store.EXPECT().Get(ctx, "own").Return(ownerTok, nil) | ||
| 286 | f.store.EXPECT().Revoke(ctx, "own").Return(errors.New("kv down")) | 302 | f.store.EXPECT().Revoke(ctx, "own").Return(errors.New("kv down")) | ||
| @@ -288,6 +304,7 @@ func TestTokensRevokeOwnerRightsEndsWebSessions(t *testing.T) { | |||||
| 288 | }) | 304 | }) | ||
| 289 | 305 | ||||
| 290 | t.Run("a token out of reach is not revoked and bumps nothing", func(t *testing.T) { | 306 | t.Run("a token out of reach is not revoked and bumps nothing", func(t *testing.T) { | ||
| 307 | + | t.Parallel() | |||
| 291 | f := newTokenFixture(t) | 308 | f := newTokenFixture(t) | ||
| 292 | limited := domain.TokenPrincipal(domain.Token{ID: "adm", Scopes: scopes(domain.ScopeTokenAdmin)}) | 309 | limited := domain.TokenPrincipal(domain.Token{ID: "adm", Scopes: scopes(domain.ScopeTokenAdmin)}) | ||
| 293 | f.store.EXPECT().Get(ctx, "own").Return(ownerTok, nil) | 310 | f.store.EXPECT().Get(ctx, "own").Return(ownerTok, nil) | ||
| @@ -295,6 +312,7 @@ func TestTokensRevokeOwnerRightsEndsWebSessions(t *testing.T) { | |||||
| 295 | }) | 312 | }) | ||
| 296 | 313 | ||||
| 297 | t.Run("a failure to sign out is reported once the token is revoked", func(t *testing.T) { | 314 | t.Run("a failure to sign out is reported once the token is revoked", func(t *testing.T) { | ||
| 315 | + | t.Parallel() | |||
| 298 | f := newTokenFixture(t) | 316 | f := newTokenFixture(t) | ||
| 299 | f.store.EXPECT().Get(ctx, "own").Return(ownerTok, nil) | 317 | f.store.EXPECT().Get(ctx, "own").Return(ownerTok, nil) | ||
| 300 | f.store.EXPECT().Revoke(ctx, "own").Return(nil) | 318 | f.store.EXPECT().Revoke(ctx, "own").Return(nil) | ||
| @@ -306,6 +324,7 @@ func TestTokensRevokeOwnerRightsEndsWebSessions(t *testing.T) { | |||||
| 306 | // A token admin sees and revokes only the tokens it could have created: the | 324 | // A token admin sees and revokes only the tokens it could have created: the | ||
| 307 | // no-escalation rule of Create, applied to scopes, repositories and expiry. | 325 | // no-escalation rule of Create, applied to scopes, repositories and expiry. | ||
| 308 | func TestTokensAdminReach(t *testing.T) { | 326 | func TestTokensAdminReach(t *testing.T) { | ||
| 327 | + | t.Parallel() | |||
| 309 | ctx := context.Background() | 328 | ctx := context.Background() | ||
| 310 | exp := now.Add(48 * time.Hour) | 329 | exp := now.Add(48 * time.Hour) | ||
| 311 | admin := func(repos []domain.RepoName, expires time.Time, extra ...domain.Scope) domain.Principal { | 330 | admin := func(repos []domain.RepoName, expires time.Time, extra ...domain.Scope) domain.Principal { | ||
| @@ -343,6 +362,7 @@ func TestTokensAdminReach(t *testing.T) { | |||||
| 343 | } | 362 | } | ||
| 344 | for _, tt := range tests { | 363 | for _, tt := range tests { | ||
| 345 | t.Run("list: "+tt.name, func(t *testing.T) { | 364 | t.Run("list: "+tt.name, func(t *testing.T) { | ||
| 365 | + | t.Parallel() | |||
| 346 | f := newTokenFixture(t) | 366 | f := newTokenFixture(t) | ||
| 347 | other := tok("other", scopes(domain.ScopeRepoRead), nil, time.Time{}) | 367 | other := tok("other", scopes(domain.ScopeRepoRead), nil, time.Time{}) | ||
| 348 | f.store.EXPECT().List(ctx).Return([]domain.Token{tt.target, other}, nil) | 368 | f.store.EXPECT().List(ctx).Return([]domain.Token{tt.target, other}, nil) | ||
| @@ -356,6 +376,7 @@ func TestTokensAdminReach(t *testing.T) { | |||||
| 356 | assert.Contains(t, got, tt.target) | 376 | assert.Contains(t, got, tt.target) | ||
| 357 | }) | 377 | }) | ||
| 358 | t.Run("revoke: "+tt.name, func(t *testing.T) { | 378 | t.Run("revoke: "+tt.name, func(t *testing.T) { | ||
| 379 | + | t.Parallel() | |||
| 359 | f := newTokenFixture(t) | 380 | f := newTokenFixture(t) | ||
| 360 | f.store.EXPECT().Get(ctx, "t").Return(tt.target, nil) | 381 | f.store.EXPECT().Get(ctx, "t").Return(tt.target, nil) | ||
| 361 | if tt.within { | 382 | if tt.within { | ||
| @@ -369,6 +390,7 @@ func TestTokensAdminReach(t *testing.T) { | |||||
| 369 | } | 390 | } | ||
| 370 | 391 | ||||
| 371 | t.Run("root lists and revokes everything", func(t *testing.T) { | 392 | t.Run("root lists and revokes everything", func(t *testing.T) { | ||
| 393 | + | t.Parallel() | |||
| 372 | f := newTokenFixture(t) | 394 | f := newTokenFixture(t) | ||
| 373 | all := []domain.Token{tok("x", domain.AllScopes(), nil, time.Time{}), tok("y", scopes(domain.ScopeMRMerge), a, exp)} | 395 | all := []domain.Token{tok("x", domain.AllScopes(), nil, time.Time{}), tok("y", scopes(domain.ScopeMRMerge), a, exp)} | ||
| 374 | f.store.EXPECT().List(ctx).Return(all, nil) | 396 | f.store.EXPECT().List(ctx).Return(all, nil) | ||
| @@ -382,12 +404,14 @@ func TestTokensAdminReach(t *testing.T) { | |||||
| 382 | }) | 404 | }) | ||
| 383 | 405 | ||||
| 384 | t.Run("an unknown id is not found for everyone", func(t *testing.T) { | 406 | t.Run("an unknown id is not found for everyone", func(t *testing.T) { | ||
| 407 | + | t.Parallel() | |||
| 385 | f := newTokenFixture(t) | 408 | f := newTokenFixture(t) | ||
| 386 | f.store.EXPECT().Get(ctx, "nope").Return(domain.Token{}, domain.ErrNotFound) | 409 | f.store.EXPECT().Get(ctx, "nope").Return(domain.Token{}, domain.ErrNotFound) | ||
| 387 | assert.ErrorIs(t, f.tokens.Revoke(ctx, admin(nil, time.Time{}), "nope"), domain.ErrNotFound) | 410 | assert.ErrorIs(t, f.tokens.Revoke(ctx, admin(nil, time.Time{}), "nope"), domain.ErrNotFound) | ||
| 388 | }) | 411 | }) | ||
| 389 | 412 | ||||
| 390 | t.Run("a store failure on the lookup is returned", func(t *testing.T) { | 413 | t.Run("a store failure on the lookup is returned", func(t *testing.T) { | ||
| 414 | + | t.Parallel() | |||
| 391 | f := newTokenFixture(t) | 415 | f := newTokenFixture(t) | ||
| 392 | boom := errors.New("boom") | 416 | boom := errors.New("boom") | ||
| 393 | f.store.EXPECT().Get(ctx, "t").Return(domain.Token{}, boom) | 417 | f.store.EXPECT().Get(ctx, "t").Return(domain.Token{}, boom) | ||
| @@ -396,33 +420,40 @@ func TestTokensAdminReach(t *testing.T) { | |||||
| 396 | } | 420 | } | ||
| 397 | 421 | ||||
| 398 | func TestAuthRevalidate(t *testing.T) { | 422 | func TestAuthRevalidate(t *testing.T) { | ||
| 423 | + | t.Parallel() | |||
| 399 | ctx := context.Background() | 424 | ctx := context.Background() | ||
| 400 | p := domain.TokenPrincipal(domain.Token{ID: "t1", Scopes: scopes(domain.ScopeRepoWrite)}) | 425 | p := domain.TokenPrincipal(domain.Token{ID: "t1", Scopes: scopes(domain.ScopeRepoWrite)}) | ||
| 401 | 426 | ||||
| 402 | t.Run("root is always valid", func(t *testing.T) { | 427 | t.Run("root is always valid", func(t *testing.T) { | ||
| 428 | + | t.Parallel() | |||
| 403 | f := newTokenFixture(t) | 429 | f := newTokenFixture(t) | ||
| 404 | require.NoError(t, f.auth.Revalidate(ctx, domain.Root())) | 430 | require.NoError(t, f.auth.Revalidate(ctx, domain.Root())) | ||
| 405 | }) | 431 | }) | ||
| 406 | t.Run("anonymous is not", func(t *testing.T) { | 432 | t.Run("anonymous is not", func(t *testing.T) { | ||
| 433 | + | t.Parallel() | |||
| 407 | f := newTokenFixture(t) | 434 | f := newTokenFixture(t) | ||
| 408 | assert.ErrorIs(t, f.auth.Revalidate(ctx, domain.Anonymous()), domain.ErrUnauthenticated) | 435 | assert.ErrorIs(t, f.auth.Revalidate(ctx, domain.Anonymous()), domain.ErrUnauthenticated) | ||
| 409 | }) | 436 | }) | ||
| 410 | t.Run("a stored token that exists", func(t *testing.T) { | 437 | t.Run("a stored token that exists", func(t *testing.T) { | ||
| 438 | + | t.Parallel() | |||
| 411 | f := newTokenFixture(t) | 439 | f := newTokenFixture(t) | ||
| 412 | f.store.EXPECT().Get(ctx, "t1").Return(domain.Token{ID: "t1", ExpiresAt: now.Add(time.Hour)}, nil) | 440 | f.store.EXPECT().Get(ctx, "t1").Return(domain.Token{ID: "t1", ExpiresAt: now.Add(time.Hour)}, nil) | ||
| 413 | require.NoError(t, f.auth.Revalidate(ctx, p)) | 441 | require.NoError(t, f.auth.Revalidate(ctx, p)) | ||
| 414 | }) | 442 | }) | ||
| 415 | t.Run("revoked", func(t *testing.T) { | 443 | t.Run("revoked", func(t *testing.T) { | ||
| 444 | + | t.Parallel() | |||
| 416 | f := newTokenFixture(t) | 445 | f := newTokenFixture(t) | ||
| 417 | f.store.EXPECT().Get(ctx, "t1").Return(domain.Token{}, domain.ErrNotFound) | 446 | f.store.EXPECT().Get(ctx, "t1").Return(domain.Token{}, domain.ErrNotFound) | ||
| 418 | assert.ErrorIs(t, f.auth.Revalidate(ctx, p), domain.ErrUnauthenticated) | 447 | assert.ErrorIs(t, f.auth.Revalidate(ctx, p), domain.ErrUnauthenticated) | ||
| 419 | }) | 448 | }) | ||
| 420 | t.Run("expired", func(t *testing.T) { | 449 | t.Run("expired", func(t *testing.T) { | ||
| 450 | + | t.Parallel() | |||
| 421 | f := newTokenFixture(t) | 451 | f := newTokenFixture(t) | ||
| 422 | f.store.EXPECT().Get(ctx, "t1").Return(domain.Token{ID: "t1", ExpiresAt: now}, nil) | 452 | f.store.EXPECT().Get(ctx, "t1").Return(domain.Token{ID: "t1", ExpiresAt: now}, nil) | ||
| 423 | assert.ErrorIs(t, f.auth.Revalidate(ctx, p), domain.ErrUnauthenticated) | 453 | assert.ErrorIs(t, f.auth.Revalidate(ctx, p), domain.ErrUnauthenticated) | ||
| 424 | }) | 454 | }) | ||
| 425 | t.Run("a store failure is an error, not a pass", func(t *testing.T) { | 455 | t.Run("a store failure is an error, not a pass", func(t *testing.T) { | ||
| 456 | + | t.Parallel() | |||
| 426 | f := newTokenFixture(t) | 457 | f := newTokenFixture(t) | ||
| 427 | boom := errors.New("boom") | 458 | boom := errors.New("boom") | ||
| 428 | f.store.EXPECT().Get(ctx, "t1").Return(domain.Token{}, boom) | 459 | f.store.EXPECT().Get(ctx, "t1").Return(domain.Token{}, boom) | ||
| @@ -433,6 +464,7 @@ func TestAuthRevalidate(t *testing.T) { | |||||
| 433 | } | 464 | } | ||
| 434 | 465 | ||||
| 435 | func TestAuthenticate(t *testing.T) { | 466 | func TestAuthenticate(t *testing.T) { | ||
| 467 | + | t.Parallel() | |||
| 436 | ctx := context.Background() | 468 | ctx := context.Background() | ||
| 437 | secret := "ermite_stored-secret" | 469 | secret := "ermite_stored-secret" | ||
| 438 | hash := domain.HashTokenSecret(secret) | 470 | hash := domain.HashTokenSecret(secret) | ||
| @@ -442,6 +474,7 @@ func TestAuthenticate(t *testing.T) { | |||||
| 442 | } | 474 | } | ||
| 443 | 475 | ||||
| 444 | t.Run("root", func(t *testing.T) { | 476 | t.Run("root", func(t *testing.T) { | ||
| 477 | + | t.Parallel() | |||
| 445 | f := newTokenFixture(t) | 478 | f := newTokenFixture(t) | ||
| 446 | p, err := f.auth.Authenticate(ctx, rootSecret) | 479 | p, err := f.auth.Authenticate(ctx, rootSecret) | ||
| 447 | require.NoError(t, err) | 480 | require.NoError(t, err) | ||
| @@ -449,6 +482,7 @@ func TestAuthenticate(t *testing.T) { | |||||
| 449 | }) | 482 | }) | ||
| 450 | 483 | ||||
| 451 | t.Run("empty and unknown secrets", func(t *testing.T) { | 484 | t.Run("empty and unknown secrets", func(t *testing.T) { | ||
| 485 | + | t.Parallel() | |||
| 452 | f := newTokenFixture(t) | 486 | f := newTokenFixture(t) | ||
| 453 | _, err := f.auth.Authenticate(ctx, "") | 487 | _, err := f.auth.Authenticate(ctx, "") | ||
| 454 | assert.ErrorIs(t, err, domain.ErrUnauthenticated) | 488 | assert.ErrorIs(t, err, domain.ErrUnauthenticated) | ||
| @@ -458,6 +492,7 @@ func TestAuthenticate(t *testing.T) { | |||||
| 458 | }) | 492 | }) | ||
| 459 | 493 | ||||
| 460 | t.Run("stored token first use touches", func(t *testing.T) { | 494 | t.Run("stored token first use touches", func(t *testing.T) { | ||
| 495 | + | t.Parallel() | |||
| 461 | f := newTokenFixture(t) | 496 | f := newTokenFixture(t) | ||
| 462 | f.store.EXPECT().FindByHash(ctx, hash).Return(tok, nil) | 497 | f.store.EXPECT().FindByHash(ctx, hash).Return(tok, nil) | ||
| 463 | f.store.EXPECT().Touch(ctx, "t1", now).Return(nil) | 498 | f.store.EXPECT().Touch(ctx, "t1", now).Return(nil) | ||
| @@ -469,6 +504,7 @@ func TestAuthenticate(t *testing.T) { | |||||
| 469 | }) | 504 | }) | ||
| 470 | 505 | ||||
| 471 | t.Run("touch at most once a minute", func(t *testing.T) { | 506 | t.Run("touch at most once a minute", func(t *testing.T) { | ||
| 507 | + | t.Parallel() | |||
| 472 | f := newTokenFixture(t) | 508 | f := newTokenFixture(t) | ||
| 473 | recent := tok | 509 | recent := tok | ||
| 474 | recent.LastUsedAt = now.Add(-30 * time.Second) | 510 | recent.LastUsedAt = now.Add(-30 * time.Second) | ||
| @@ -486,6 +522,7 @@ func TestAuthenticate(t *testing.T) { | |||||
| 486 | }) | 522 | }) | ||
| 487 | 523 | ||||
| 488 | t.Run("touch failure does not fail authentication", func(t *testing.T) { | 524 | t.Run("touch failure does not fail authentication", func(t *testing.T) { | ||
| 525 | + | t.Parallel() | |||
| 489 | f := newTokenFixture(t) | 526 | f := newTokenFixture(t) | ||
| 490 | f.store.EXPECT().FindByHash(ctx, hash).Return(tok, nil) | 527 | f.store.EXPECT().FindByHash(ctx, hash).Return(tok, nil) | ||
| 491 | f.store.EXPECT().Touch(ctx, "t1", now).Return(errors.New("disk")) | 528 | f.store.EXPECT().Touch(ctx, "t1", now).Return(errors.New("disk")) | ||
| @@ -494,6 +531,7 @@ func TestAuthenticate(t *testing.T) { | |||||
| 494 | }) | 531 | }) | ||
| 495 | 532 | ||||
| 496 | t.Run("expired", func(t *testing.T) { | 533 | t.Run("expired", func(t *testing.T) { | ||
| 534 | + | t.Parallel() | |||
| 497 | f := newTokenFixture(t) | 535 | f := newTokenFixture(t) | ||
| 498 | exp := tok | 536 | exp := tok | ||
| 499 | exp.ExpiresAt = now | 537 | exp.ExpiresAt = now | ||
| @@ -503,6 +541,7 @@ func TestAuthenticate(t *testing.T) { | |||||
| 503 | }) | 541 | }) | ||
| 504 | 542 | ||||
| 505 | t.Run("store error is not an authentication failure", func(t *testing.T) { | 543 | t.Run("store error is not an authentication failure", func(t *testing.T) { | ||
| 544 | + | t.Parallel() | |||
| 506 | f := newTokenFixture(t) | 545 | f := newTokenFixture(t) | ||
| 507 | boom := errors.New("boom") | 546 | boom := errors.New("boom") | ||
| 508 | f.store.EXPECT().FindByHash(ctx, hash).Return(domain.Token{}, boom) | 547 | f.store.EXPECT().FindByHash(ctx, hash).Return(domain.Token{}, boom) | ||
M internal/clientlimit/limiter_test.go +9 -0
| @@ -23,6 +23,7 @@ type fakeClock struct{ t time.Time } | |||
| 23 | 23 | func (c *fakeClock) now() time.Time { return c.t } | |
| 24 | 24 | ||
| 25 | 25 | func TestLimiter(t *testing.T) { | |
| 26 | + | t.Parallel() | |
| 26 | 27 | clk := &fakeClock{t: time.Date(2026, 10, 10, 8, 0, 0, 0, time.UTC)} | |
| 27 | 28 | l := clientlimit.NewLimiter(clk.now) | |
| 28 | 29 | ||
| @@ -52,6 +53,7 @@ func TestLimiter(t *testing.T) { | |||
| 52 | 53 | } | |
| 53 | 54 | ||
| 54 | 55 | func TestLimiterSlidingWindow(t *testing.T) { | |
| 56 | + | t.Parallel() | |
| 55 | 57 | clk := &fakeClock{t: time.Date(2026, 10, 10, 8, 0, 0, 0, time.UTC)} | |
| 56 | 58 | l := clientlimit.NewLimiter(clk.now) | |
| 57 | 59 | for range 5 { | |
| @@ -71,6 +73,7 @@ func TestLimiterSlidingWindow(t *testing.T) { | |||
| 71 | 73 | } | |
| 72 | 74 | ||
| 73 | 75 | func TestLimiterOneAddressStaysSmall(t *testing.T) { | |
| 76 | + | t.Parallel() | |
| 74 | 77 | clk := &fakeClock{t: time.Date(2026, 10, 10, 8, 0, 0, 0, time.UTC)} | |
| 75 | 78 | l := clientlimit.NewLimiter(clk.now) | |
| 76 | 79 | for range 5000 { | |
| @@ -82,6 +85,7 @@ func TestLimiterOneAddressStaysSmall(t *testing.T) { | |||
| 82 | 85 | } | |
| 83 | 86 | ||
| 84 | 87 | func TestLimiterBoundsMemory(t *testing.T) { | |
| 88 | + | t.Parallel() | |
| 85 | 89 | clk := &fakeClock{t: time.Date(2026, 10, 10, 8, 0, 0, 0, time.UTC)} | |
| 86 | 90 | l := clientlimit.NewLimiter(clk.now) | |
| 87 | 91 | for i := range clientlimit.MaxTracked + 5000 { | |
| @@ -103,6 +107,7 @@ func TestLimiterBoundsMemory(t *testing.T) { | |||
| 103 | 107 | } | |
| 104 | 108 | ||
| 105 | 109 | func TestLimiterSweepsExpiredEntriesOncePerInterval(t *testing.T) { | |
| 110 | + | t.Parallel() | |
| 106 | 111 | clk := &fakeClock{t: time.Date(2026, 10, 10, 8, 0, 0, 0, time.UTC)} | |
| 107 | 112 | l := clientlimit.NewLimiter(clk.now) | |
| 108 | 113 | for i := range 100 { | |
| @@ -135,6 +140,7 @@ func BenchmarkLimiterFailAtMaxTracked(b *testing.B) { | |||
| 135 | 140 | } | |
| 136 | 141 | ||
| 137 | 142 | func TestLimiterGroupsIPv6By64(t *testing.T) { | |
| 143 | + | t.Parallel() | |
| 138 | 144 | clk := &fakeClock{t: time.Date(2026, 10, 10, 8, 0, 0, 0, time.UTC)} | |
| 139 | 145 | l := clientlimit.NewLimiter(clk.now) | |
| 140 | 146 | ||
| @@ -155,6 +161,7 @@ func TestLimiterGroupsIPv6By64(t *testing.T) { | |||
| 155 | 161 | } | |
| 156 | 162 | ||
| 157 | 163 | func TestLimiterIPv4StaysPerAddress(t *testing.T) { | |
| 164 | + | t.Parallel() | |
| 158 | 165 | clk := &fakeClock{t: time.Date(2026, 10, 10, 8, 0, 0, 0, time.UTC)} | |
| 159 | 166 | l := clientlimit.NewLimiter(clk.now) | |
| 160 | 167 | for range clientlimit.MaxFailures { | |
| @@ -170,6 +177,7 @@ func TestLimiterIPv4StaysPerAddress(t *testing.T) { | |||
| 170 | 177 | } | |
| 171 | 178 | ||
| 172 | 179 | func TestClientAddr(t *testing.T) { | |
| 180 | + | t.Parallel() | |
| 173 | 181 | trusted := []netip.Prefix{netip.MustParsePrefix("10.0.0.0/8"), netip.MustParsePrefix("fd00::/8")} | |
| 174 | 182 | tests := []struct { | |
| 175 | 183 | name string | |
| @@ -194,6 +202,7 @@ func TestClientAddr(t *testing.T) { | |||
| 194 | 202 | } | |
| 195 | 203 | for _, tt := range tests { | |
| 196 | 204 | t.Run(tt.name, func(t *testing.T) { | |
| 205 | + | t.Parallel() | |
| 197 | 206 | r := &http.Request{RemoteAddr: tt.remote, Header: http.Header{}} | |
| 198 | 207 | for _, v := range tt.xff { | |
| 199 | 208 | r.Header.Add("X-Forwarded-For", v) | |
| @@ -23,6 +23,7 @@ type fakeClock struct{ t time.Time } | |||||
| 23 | func (c *fakeClock) now() time.Time { return c.t } | 23 | func (c *fakeClock) now() time.Time { return c.t } | ||
| 24 | 24 | ||||
| 25 | func TestLimiter(t *testing.T) { | 25 | func TestLimiter(t *testing.T) { | ||
| 26 | + | t.Parallel() | |||
| 26 | clk := &fakeClock{t: time.Date(2026, 10, 10, 8, 0, 0, 0, time.UTC)} | 27 | clk := &fakeClock{t: time.Date(2026, 10, 10, 8, 0, 0, 0, time.UTC)} | ||
| 27 | l := clientlimit.NewLimiter(clk.now) | 28 | l := clientlimit.NewLimiter(clk.now) | ||
| 28 | 29 | ||||
| @@ -52,6 +53,7 @@ func TestLimiter(t *testing.T) { | |||||
| 52 | } | 53 | } | ||
| 53 | 54 | ||||
| 54 | func TestLimiterSlidingWindow(t *testing.T) { | 55 | func TestLimiterSlidingWindow(t *testing.T) { | ||
| 56 | + | t.Parallel() | |||
| 55 | clk := &fakeClock{t: time.Date(2026, 10, 10, 8, 0, 0, 0, time.UTC)} | 57 | clk := &fakeClock{t: time.Date(2026, 10, 10, 8, 0, 0, 0, time.UTC)} | ||
| 56 | l := clientlimit.NewLimiter(clk.now) | 58 | l := clientlimit.NewLimiter(clk.now) | ||
| 57 | for range 5 { | 59 | for range 5 { | ||
| @@ -71,6 +73,7 @@ func TestLimiterSlidingWindow(t *testing.T) { | |||||
| 71 | } | 73 | } | ||
| 72 | 74 | ||||
| 73 | func TestLimiterOneAddressStaysSmall(t *testing.T) { | 75 | func TestLimiterOneAddressStaysSmall(t *testing.T) { | ||
| 76 | + | t.Parallel() | |||
| 74 | clk := &fakeClock{t: time.Date(2026, 10, 10, 8, 0, 0, 0, time.UTC)} | 77 | clk := &fakeClock{t: time.Date(2026, 10, 10, 8, 0, 0, 0, time.UTC)} | ||
| 75 | l := clientlimit.NewLimiter(clk.now) | 78 | l := clientlimit.NewLimiter(clk.now) | ||
| 76 | for range 5000 { | 79 | for range 5000 { | ||
| @@ -82,6 +85,7 @@ func TestLimiterOneAddressStaysSmall(t *testing.T) { | |||||
| 82 | } | 85 | } | ||
| 83 | 86 | ||||
| 84 | func TestLimiterBoundsMemory(t *testing.T) { | 87 | func TestLimiterBoundsMemory(t *testing.T) { | ||
| 88 | + | t.Parallel() | |||
| 85 | clk := &fakeClock{t: time.Date(2026, 10, 10, 8, 0, 0, 0, time.UTC)} | 89 | clk := &fakeClock{t: time.Date(2026, 10, 10, 8, 0, 0, 0, time.UTC)} | ||
| 86 | l := clientlimit.NewLimiter(clk.now) | 90 | l := clientlimit.NewLimiter(clk.now) | ||
| 87 | for i := range clientlimit.MaxTracked + 5000 { | 91 | for i := range clientlimit.MaxTracked + 5000 { | ||
| @@ -103,6 +107,7 @@ func TestLimiterBoundsMemory(t *testing.T) { | |||||
| 103 | } | 107 | } | ||
| 104 | 108 | ||||
| 105 | func TestLimiterSweepsExpiredEntriesOncePerInterval(t *testing.T) { | 109 | func TestLimiterSweepsExpiredEntriesOncePerInterval(t *testing.T) { | ||
| 110 | + | t.Parallel() | |||
| 106 | clk := &fakeClock{t: time.Date(2026, 10, 10, 8, 0, 0, 0, time.UTC)} | 111 | clk := &fakeClock{t: time.Date(2026, 10, 10, 8, 0, 0, 0, time.UTC)} | ||
| 107 | l := clientlimit.NewLimiter(clk.now) | 112 | l := clientlimit.NewLimiter(clk.now) | ||
| 108 | for i := range 100 { | 113 | for i := range 100 { | ||
| @@ -135,6 +140,7 @@ func BenchmarkLimiterFailAtMaxTracked(b *testing.B) { | |||||
| 135 | } | 140 | } | ||
| 136 | 141 | ||||
| 137 | func TestLimiterGroupsIPv6By64(t *testing.T) { | 142 | func TestLimiterGroupsIPv6By64(t *testing.T) { | ||
| 143 | + | t.Parallel() | |||
| 138 | clk := &fakeClock{t: time.Date(2026, 10, 10, 8, 0, 0, 0, time.UTC)} | 144 | clk := &fakeClock{t: time.Date(2026, 10, 10, 8, 0, 0, 0, time.UTC)} | ||
| 139 | l := clientlimit.NewLimiter(clk.now) | 145 | l := clientlimit.NewLimiter(clk.now) | ||
| 140 | 146 | ||||
| @@ -155,6 +161,7 @@ func TestLimiterGroupsIPv6By64(t *testing.T) { | |||||
| 155 | } | 161 | } | ||
| 156 | 162 | ||||
| 157 | func TestLimiterIPv4StaysPerAddress(t *testing.T) { | 163 | func TestLimiterIPv4StaysPerAddress(t *testing.T) { | ||
| 164 | + | t.Parallel() | |||
| 158 | clk := &fakeClock{t: time.Date(2026, 10, 10, 8, 0, 0, 0, time.UTC)} | 165 | clk := &fakeClock{t: time.Date(2026, 10, 10, 8, 0, 0, 0, time.UTC)} | ||
| 159 | l := clientlimit.NewLimiter(clk.now) | 166 | l := clientlimit.NewLimiter(clk.now) | ||
| 160 | for range clientlimit.MaxFailures { | 167 | for range clientlimit.MaxFailures { | ||
| @@ -170,6 +177,7 @@ func TestLimiterIPv4StaysPerAddress(t *testing.T) { | |||||
| 170 | } | 177 | } | ||
| 171 | 178 | ||||
| 172 | func TestClientAddr(t *testing.T) { | 179 | func TestClientAddr(t *testing.T) { | ||
| 180 | + | t.Parallel() | |||
| 173 | trusted := []netip.Prefix{netip.MustParsePrefix("10.0.0.0/8"), netip.MustParsePrefix("fd00::/8")} | 181 | trusted := []netip.Prefix{netip.MustParsePrefix("10.0.0.0/8"), netip.MustParsePrefix("fd00::/8")} | ||
| 174 | tests := []struct { | 182 | tests := []struct { | ||
| 175 | name string | 183 | name string | ||
| @@ -194,6 +202,7 @@ func TestClientAddr(t *testing.T) { | |||||
| 194 | } | 202 | } | ||
| 195 | for _, tt := range tests { | 203 | for _, tt := range tests { | ||
| 196 | t.Run(tt.name, func(t *testing.T) { | 204 | t.Run(tt.name, func(t *testing.T) { | ||
| 205 | + | t.Parallel() | |||
| 197 | r := &http.Request{RemoteAddr: tt.remote, Header: http.Header{}} | 206 | r := &http.Request{RemoteAddr: tt.remote, Header: http.Header{}} | ||
| 198 | for _, v := range tt.xff { | 207 | for _, v := range tt.xff { | ||
| 199 | r.Header.Add("X-Forwarded-For", v) | 208 | r.Header.Add("X-Forwarded-For", v) | ||
M internal/domain/event_test.go +2 -0
| @@ -13,6 +13,7 @@ import ( | |||
| 13 | 13 | ) | |
| 14 | 14 | ||
| 15 | 15 | func TestNewRefUpdate(t *testing.T) { | |
| 16 | + | t.Parallel() | |
| 16 | 17 | a, b := strings.Repeat("a", 40), strings.Repeat("b", 40) | |
| 17 | 18 | ||
| 18 | 19 | u, err := domain.NewRefUpdate(domain.ZeroOID, a, "refs/heads/main") | |
| @@ -46,6 +47,7 @@ func TestNewRefUpdate(t *testing.T) { | |||
| 46 | 47 | } | |
| 47 | 48 | ||
| 48 | 49 | func TestEventKinds(t *testing.T) { | |
| 50 | + | t.Parallel() | |
| 49 | 51 | assert.Equal(t, "repo.created", domain.RepoCreated{}.EventKind()) | |
| 50 | 52 | assert.Equal(t, "repo.pushed", domain.PushEvent{}.EventKind()) | |
| 51 | 53 | assert.Equal(t, "gap", domain.EventGap{}.EventKind()) | |
| @@ -13,6 +13,7 @@ import ( | |||||
| 13 | ) | 13 | ) | ||
| 14 | 14 | ||||
| 15 | func TestNewRefUpdate(t *testing.T) { | 15 | func TestNewRefUpdate(t *testing.T) { | ||
| 16 | + | t.Parallel() | |||
| 16 | a, b := strings.Repeat("a", 40), strings.Repeat("b", 40) | 17 | a, b := strings.Repeat("a", 40), strings.Repeat("b", 40) | ||
| 17 | 18 | ||||
| 18 | u, err := domain.NewRefUpdate(domain.ZeroOID, a, "refs/heads/main") | 19 | u, err := domain.NewRefUpdate(domain.ZeroOID, a, "refs/heads/main") | ||
| @@ -46,6 +47,7 @@ func TestNewRefUpdate(t *testing.T) { | |||||
| 46 | } | 47 | } | ||
| 47 | 48 | ||||
| 48 | func TestEventKinds(t *testing.T) { | 49 | func TestEventKinds(t *testing.T) { | ||
| 50 | + | t.Parallel() | |||
| 49 | assert.Equal(t, "repo.created", domain.RepoCreated{}.EventKind()) | 51 | assert.Equal(t, "repo.created", domain.RepoCreated{}.EventKind()) | ||
| 50 | assert.Equal(t, "repo.pushed", domain.PushEvent{}.EventKind()) | 52 | assert.Equal(t, "repo.pushed", domain.PushEvent{}.EventKind()) | ||
| 51 | assert.Equal(t, "gap", domain.EventGap{}.EventKind()) | 53 | assert.Equal(t, "gap", domain.EventGap{}.EventKind()) | ||
M internal/domain/git_test.go +6 -0
| @@ -13,6 +13,7 @@ import ( | |||
| 13 | 13 | ) | |
| 14 | 14 | ||
| 15 | 15 | func TestParseObjectID(t *testing.T) { | |
| 16 | + | t.Parallel() | |
| 16 | 17 | good := strings.Repeat("ab12", 10) | |
| 17 | 18 | id, err := domain.ParseObjectID(good) | |
| 18 | 19 | require.NoError(t, err) | |
| @@ -27,6 +28,7 @@ func TestParseObjectID(t *testing.T) { | |||
| 27 | 28 | } | |
| 28 | 29 | ||
| 29 | 30 | func TestParseRevision(t *testing.T) { | |
| 31 | + | t.Parallel() | |
| 30 | 32 | valid := []string{ | |
| 31 | 33 | "", "main", "feature/x", "refs/heads/feature/x", "v1.0.0", "HEAD", | |
| 32 | 34 | "abc1234", strings.Repeat("a", 40), "release-2026.10", "café", | |
| @@ -47,6 +49,7 @@ func TestParseRevision(t *testing.T) { | |||
| 47 | 49 | } | |
| 48 | 50 | ||
| 49 | 51 | func TestRevisionKinds(t *testing.T) { | |
| 52 | + | t.Parallel() | |
| 50 | 53 | head, _ := domain.ParseRevision("") | |
| 51 | 54 | assert.True(t, head.IsHead()) | |
| 52 | 55 | full, _ := domain.ParseRevision(strings.Repeat("a", 40)) | |
| @@ -64,6 +67,7 @@ func TestRevisionKinds(t *testing.T) { | |||
| 64 | 67 | } | |
| 65 | 68 | ||
| 66 | 69 | func TestParseTreePath(t *testing.T) { | |
| 70 | + | t.Parallel() | |
| 67 | 71 | root, err := domain.ParseTreePath("") | |
| 68 | 72 | require.NoError(t, err) | |
| 69 | 73 | assert.True(t, root.IsRoot()) | |
| @@ -81,6 +85,7 @@ func TestParseTreePath(t *testing.T) { | |||
| 81 | 85 | } | |
| 82 | 86 | ||
| 83 | 87 | func TestEntryKindOf(t *testing.T) { | |
| 88 | + | t.Parallel() | |
| 84 | 89 | tests := map[uint32]domain.EntryKind{ | |
| 85 | 90 | 0o100644: domain.EntryFile, | |
| 86 | 91 | 0o100664: domain.EntryFile, | |
| @@ -97,5 +102,6 @@ func TestEntryKindOf(t *testing.T) { | |||
| 97 | 102 | } | |
| 98 | 103 | ||
| 99 | 104 | func TestDefaultDiffLimits(t *testing.T) { | |
| 105 | + | t.Parallel() | |
| 100 | 106 | assert.Equal(t, domain.DiffLimits{MaxFiles: 300, MaxFileLines: 2000, MaxTotalLines: 20000, MaxTotalBytes: 8 << 20}, domain.DefaultDiffLimits()) | |
| 101 | 107 | } | |
| @@ -13,6 +13,7 @@ import ( | |||||
| 13 | ) | 13 | ) | ||
| 14 | 14 | ||||
| 15 | func TestParseObjectID(t *testing.T) { | 15 | func TestParseObjectID(t *testing.T) { | ||
| 16 | + | t.Parallel() | |||
| 16 | good := strings.Repeat("ab12", 10) | 17 | good := strings.Repeat("ab12", 10) | ||
| 17 | id, err := domain.ParseObjectID(good) | 18 | id, err := domain.ParseObjectID(good) | ||
| 18 | require.NoError(t, err) | 19 | require.NoError(t, err) | ||
| @@ -27,6 +28,7 @@ func TestParseObjectID(t *testing.T) { | |||||
| 27 | } | 28 | } | ||
| 28 | 29 | ||||
| 29 | func TestParseRevision(t *testing.T) { | 30 | func TestParseRevision(t *testing.T) { | ||
| 31 | + | t.Parallel() | |||
| 30 | valid := []string{ | 32 | valid := []string{ | ||
| 31 | "", "main", "feature/x", "refs/heads/feature/x", "v1.0.0", "HEAD", | 33 | "", "main", "feature/x", "refs/heads/feature/x", "v1.0.0", "HEAD", | ||
| 32 | "abc1234", strings.Repeat("a", 40), "release-2026.10", "café", | 34 | "abc1234", strings.Repeat("a", 40), "release-2026.10", "café", | ||
| @@ -47,6 +49,7 @@ func TestParseRevision(t *testing.T) { | |||||
| 47 | } | 49 | } | ||
| 48 | 50 | ||||
| 49 | func TestRevisionKinds(t *testing.T) { | 51 | func TestRevisionKinds(t *testing.T) { | ||
| 52 | + | t.Parallel() | |||
| 50 | head, _ := domain.ParseRevision("") | 53 | head, _ := domain.ParseRevision("") | ||
| 51 | assert.True(t, head.IsHead()) | 54 | assert.True(t, head.IsHead()) | ||
| 52 | full, _ := domain.ParseRevision(strings.Repeat("a", 40)) | 55 | full, _ := domain.ParseRevision(strings.Repeat("a", 40)) | ||
| @@ -64,6 +67,7 @@ func TestRevisionKinds(t *testing.T) { | |||||
| 64 | } | 67 | } | ||
| 65 | 68 | ||||
| 66 | func TestParseTreePath(t *testing.T) { | 69 | func TestParseTreePath(t *testing.T) { | ||
| 70 | + | t.Parallel() | |||
| 67 | root, err := domain.ParseTreePath("") | 71 | root, err := domain.ParseTreePath("") | ||
| 68 | require.NoError(t, err) | 72 | require.NoError(t, err) | ||
| 69 | assert.True(t, root.IsRoot()) | 73 | assert.True(t, root.IsRoot()) | ||
| @@ -81,6 +85,7 @@ func TestParseTreePath(t *testing.T) { | |||||
| 81 | } | 85 | } | ||
| 82 | 86 | ||||
| 83 | func TestEntryKindOf(t *testing.T) { | 87 | func TestEntryKindOf(t *testing.T) { | ||
| 88 | + | t.Parallel() | |||
| 84 | tests := map[uint32]domain.EntryKind{ | 89 | tests := map[uint32]domain.EntryKind{ | ||
| 85 | 0o100644: domain.EntryFile, | 90 | 0o100644: domain.EntryFile, | ||
| 86 | 0o100664: domain.EntryFile, | 91 | 0o100664: domain.EntryFile, | ||
| @@ -97,5 +102,6 @@ func TestEntryKindOf(t *testing.T) { | |||||
| 97 | } | 102 | } | ||
| 98 | 103 | ||||
| 99 | func TestDefaultDiffLimits(t *testing.T) { | 104 | func TestDefaultDiffLimits(t *testing.T) { | ||
| 105 | + | t.Parallel() | |||
| 100 | assert.Equal(t, domain.DiffLimits{MaxFiles: 300, MaxFileLines: 2000, MaxTotalLines: 20000, MaxTotalBytes: 8 << 20}, domain.DefaultDiffLimits()) | 106 | assert.Equal(t, domain.DiffLimits{MaxFiles: 300, MaxFileLines: 2000, MaxTotalLines: 20000, MaxTotalBytes: 8 << 20}, domain.DefaultDiffLimits()) | ||
| 101 | } | 107 | } | ||
M internal/domain/mergerequest_test.go +10 -0
| @@ -21,6 +21,7 @@ func oid(t *testing.T, c byte) domain.ObjectID { | |||
| 21 | 21 | } | |
| 22 | 22 | ||
| 23 | 23 | func TestDefaultRepoSettings(t *testing.T) { | |
| 24 | + | t.Parallel() | |
| 24 | 25 | s := domain.DefaultRepoSettings() | |
| 25 | 26 | assert.Equal(t, []domain.MergeMode{domain.MergeModeFastForward, domain.MergeModeSquash}, s.AllowedMergeModes) | |
| 26 | 27 | assert.True(t, s.FailingChecksBlock) | |
| @@ -35,6 +36,7 @@ func TestDefaultRepoSettings(t *testing.T) { | |||
| 35 | 36 | } | |
| 36 | 37 | ||
| 37 | 38 | func TestNormalizeMergeModes(t *testing.T) { | |
| 39 | + | t.Parallel() | |
| 38 | 40 | got, err := domain.NormalizeMergeModes([]domain.MergeMode{domain.MergeModeSquash, domain.MergeModeFastForward, domain.MergeModeSquash}) | |
| 39 | 41 | require.NoError(t, err) | |
| 40 | 42 | assert.Equal(t, domain.MergeModes(), got) | |
| @@ -50,6 +52,7 @@ func TestNormalizeMergeModes(t *testing.T) { | |||
| 50 | 52 | } | |
| 51 | 53 | ||
| 52 | 54 | func TestParseMergeMode(t *testing.T) { | |
| 55 | + | t.Parallel() | |
| 53 | 56 | m, err := domain.ParseMergeMode("fast-forward") | |
| 54 | 57 | require.NoError(t, err) | |
| 55 | 58 | assert.Equal(t, domain.MergeModeFastForward, m) | |
| @@ -78,6 +81,7 @@ func hunkDiff() domain.Diff { | |||
| 78 | 81 | } | |
| 79 | 82 | ||
| 80 | 83 | func TestDiffHasLine(t *testing.T) { | |
| 84 | + | t.Parallel() | |
| 81 | 85 | d := hunkDiff() | |
| 82 | 86 | tests := []struct { | |
| 83 | 87 | name string | |
| @@ -106,6 +110,7 @@ func TestDiffHasLine(t *testing.T) { | |||
| 106 | 110 | } | |
| 107 | 111 | for _, tc := range tests { | |
| 108 | 112 | t.Run(tc.name, func(t *testing.T) { | |
| 113 | + | t.Parallel() | |
| 109 | 114 | assert.Equal(t, tc.want, d.HasLine(tc.path, tc.side, tc.line)) | |
| 110 | 115 | }) | |
| 111 | 116 | } | |
| @@ -116,6 +121,7 @@ func sig(name, email string, sec int64) domain.Signature { | |||
| 116 | 121 | } | |
| 117 | 122 | ||
| 118 | 123 | func TestNewSquashCommit(t *testing.T) { | |
| 124 | + | t.Parallel() | |
| 119 | 125 | title, err := domain.ParseMRTitle("Add search") | |
| 120 | 126 | require.NoError(t, err) | |
| 121 | 127 | mr := domain.MergeRequest{Number: 7, Title: title} | |
| @@ -151,6 +157,7 @@ func TestNewSquashCommit(t *testing.T) { | |||
| 151 | 157 | } | |
| 152 | 158 | ||
| 153 | 159 | func TestSquashCommitSigned(t *testing.T) { | |
| 160 | + | t.Parallel() | |
| 154 | 161 | title, err := domain.ParseMRTitle("T") | |
| 155 | 162 | require.NoError(t, err) | |
| 156 | 163 | c := domain.NewSquashCommit(domain.MergeRequest{Number: 1, Title: title}, oid(t, 'a'), oid(t, 'b'), | |
| @@ -169,6 +176,7 @@ func TestSquashCommitSigned(t *testing.T) { | |||
| 169 | 176 | } | |
| 170 | 177 | ||
| 171 | 178 | func TestSquashCommitCleansIdentitiesAndSummaries(t *testing.T) { | |
| 179 | + | t.Parallel() | |
| 172 | 180 | title, err := domain.ParseMRTitle("T") | |
| 173 | 181 | require.NoError(t, err) | |
| 174 | 182 | commits := []domain.Commit{ | |
| @@ -183,6 +191,7 @@ func TestSquashCommitCleansIdentitiesAndSummaries(t *testing.T) { | |||
| 183 | 191 | } | |
| 184 | 192 | ||
| 185 | 193 | func TestSigningKeyInfoAllowedSigners(t *testing.T) { | |
| 194 | + | t.Parallel() | |
| 186 | 195 | i := domain.SigningKeyInfo{ | |
| 187 | 196 | Key: domain.SigningPublicKey{AuthorizedKey: "ssh-ed25519 AAAA"}, | |
| 188 | 197 | Committer: domain.Identity{Name: "ermite", Email: "ermite@git.example.org"}, | |
| @@ -191,6 +200,7 @@ func TestSigningKeyInfoAllowedSigners(t *testing.T) { | |||
| 191 | 200 | } | |
| 192 | 201 | ||
| 193 | 202 | func TestParseBranchName(t *testing.T) { | |
| 203 | + | t.Parallel() | |
| 194 | 204 | for _, ok := range []string{"main", "feat/x", "v1.2", "a-b_c"} { | |
| 195 | 205 | got, err := domain.ParseBranchName(ok) | |
| 196 | 206 | require.NoError(t, err, ok) | |
| @@ -21,6 +21,7 @@ func oid(t *testing.T, c byte) domain.ObjectID { | |||||
| 21 | } | 21 | } | ||
| 22 | 22 | ||||
| 23 | func TestDefaultRepoSettings(t *testing.T) { | 23 | func TestDefaultRepoSettings(t *testing.T) { | ||
| 24 | + | t.Parallel() | |||
| 24 | s := domain.DefaultRepoSettings() | 25 | s := domain.DefaultRepoSettings() | ||
| 25 | assert.Equal(t, []domain.MergeMode{domain.MergeModeFastForward, domain.MergeModeSquash}, s.AllowedMergeModes) | 26 | assert.Equal(t, []domain.MergeMode{domain.MergeModeFastForward, domain.MergeModeSquash}, s.AllowedMergeModes) | ||
| 26 | assert.True(t, s.FailingChecksBlock) | 27 | assert.True(t, s.FailingChecksBlock) | ||
| @@ -35,6 +36,7 @@ func TestDefaultRepoSettings(t *testing.T) { | |||||
| 35 | } | 36 | } | ||
| 36 | 37 | ||||
| 37 | func TestNormalizeMergeModes(t *testing.T) { | 38 | func TestNormalizeMergeModes(t *testing.T) { | ||
| 39 | + | t.Parallel() | |||
| 38 | got, err := domain.NormalizeMergeModes([]domain.MergeMode{domain.MergeModeSquash, domain.MergeModeFastForward, domain.MergeModeSquash}) | 40 | got, err := domain.NormalizeMergeModes([]domain.MergeMode{domain.MergeModeSquash, domain.MergeModeFastForward, domain.MergeModeSquash}) | ||
| 39 | require.NoError(t, err) | 41 | require.NoError(t, err) | ||
| 40 | assert.Equal(t, domain.MergeModes(), got) | 42 | assert.Equal(t, domain.MergeModes(), got) | ||
| @@ -50,6 +52,7 @@ func TestNormalizeMergeModes(t *testing.T) { | |||||
| 50 | } | 52 | } | ||
| 51 | 53 | ||||
| 52 | func TestParseMergeMode(t *testing.T) { | 54 | func TestParseMergeMode(t *testing.T) { | ||
| 55 | + | t.Parallel() | |||
| 53 | m, err := domain.ParseMergeMode("fast-forward") | 56 | m, err := domain.ParseMergeMode("fast-forward") | ||
| 54 | require.NoError(t, err) | 57 | require.NoError(t, err) | ||
| 55 | assert.Equal(t, domain.MergeModeFastForward, m) | 58 | assert.Equal(t, domain.MergeModeFastForward, m) | ||
| @@ -78,6 +81,7 @@ func hunkDiff() domain.Diff { | |||||
| 78 | } | 81 | } | ||
| 79 | 82 | ||||
| 80 | func TestDiffHasLine(t *testing.T) { | 83 | func TestDiffHasLine(t *testing.T) { | ||
| 84 | + | t.Parallel() | |||
| 81 | d := hunkDiff() | 85 | d := hunkDiff() | ||
| 82 | tests := []struct { | 86 | tests := []struct { | ||
| 83 | name string | 87 | name string | ||
| @@ -106,6 +110,7 @@ func TestDiffHasLine(t *testing.T) { | |||||
| 106 | } | 110 | } | ||
| 107 | for _, tc := range tests { | 111 | for _, tc := range tests { | ||
| 108 | t.Run(tc.name, func(t *testing.T) { | 112 | t.Run(tc.name, func(t *testing.T) { | ||
| 113 | + | t.Parallel() | |||
| 109 | assert.Equal(t, tc.want, d.HasLine(tc.path, tc.side, tc.line)) | 114 | assert.Equal(t, tc.want, d.HasLine(tc.path, tc.side, tc.line)) | ||
| 110 | }) | 115 | }) | ||
| 111 | } | 116 | } | ||
| @@ -116,6 +121,7 @@ func sig(name, email string, sec int64) domain.Signature { | |||||
| 116 | } | 121 | } | ||
| 117 | 122 | ||||
| 118 | func TestNewSquashCommit(t *testing.T) { | 123 | func TestNewSquashCommit(t *testing.T) { | ||
| 124 | + | t.Parallel() | |||
| 119 | title, err := domain.ParseMRTitle("Add search") | 125 | title, err := domain.ParseMRTitle("Add search") | ||
| 120 | require.NoError(t, err) | 126 | require.NoError(t, err) | ||
| 121 | mr := domain.MergeRequest{Number: 7, Title: title} | 127 | mr := domain.MergeRequest{Number: 7, Title: title} | ||
| @@ -151,6 +157,7 @@ func TestNewSquashCommit(t *testing.T) { | |||||
| 151 | } | 157 | } | ||
| 152 | 158 | ||||
| 153 | func TestSquashCommitSigned(t *testing.T) { | 159 | func TestSquashCommitSigned(t *testing.T) { | ||
| 160 | + | t.Parallel() | |||
| 154 | title, err := domain.ParseMRTitle("T") | 161 | title, err := domain.ParseMRTitle("T") | ||
| 155 | require.NoError(t, err) | 162 | require.NoError(t, err) | ||
| 156 | c := domain.NewSquashCommit(domain.MergeRequest{Number: 1, Title: title}, oid(t, 'a'), oid(t, 'b'), | 163 | c := domain.NewSquashCommit(domain.MergeRequest{Number: 1, Title: title}, oid(t, 'a'), oid(t, 'b'), | ||
| @@ -169,6 +176,7 @@ func TestSquashCommitSigned(t *testing.T) { | |||||
| 169 | } | 176 | } | ||
| 170 | 177 | ||||
| 171 | func TestSquashCommitCleansIdentitiesAndSummaries(t *testing.T) { | 178 | func TestSquashCommitCleansIdentitiesAndSummaries(t *testing.T) { | ||
| 179 | + | t.Parallel() | |||
| 172 | title, err := domain.ParseMRTitle("T") | 180 | title, err := domain.ParseMRTitle("T") | ||
| 173 | require.NoError(t, err) | 181 | require.NoError(t, err) | ||
| 174 | commits := []domain.Commit{ | 182 | commits := []domain.Commit{ | ||
| @@ -183,6 +191,7 @@ func TestSquashCommitCleansIdentitiesAndSummaries(t *testing.T) { | |||||
| 183 | } | 191 | } | ||
| 184 | 192 | ||||
| 185 | func TestSigningKeyInfoAllowedSigners(t *testing.T) { | 193 | func TestSigningKeyInfoAllowedSigners(t *testing.T) { | ||
| 194 | + | t.Parallel() | |||
| 186 | i := domain.SigningKeyInfo{ | 195 | i := domain.SigningKeyInfo{ | ||
| 187 | Key: domain.SigningPublicKey{AuthorizedKey: "ssh-ed25519 AAAA"}, | 196 | Key: domain.SigningPublicKey{AuthorizedKey: "ssh-ed25519 AAAA"}, | ||
| 188 | Committer: domain.Identity{Name: "ermite", Email: "ermite@git.example.org"}, | 197 | Committer: domain.Identity{Name: "ermite", Email: "ermite@git.example.org"}, | ||
| @@ -191,6 +200,7 @@ func TestSigningKeyInfoAllowedSigners(t *testing.T) { | |||||
| 191 | } | 200 | } | ||
| 192 | 201 | ||||
| 193 | func TestParseBranchName(t *testing.T) { | 202 | func TestParseBranchName(t *testing.T) { | ||
| 203 | + | t.Parallel() | |||
| 194 | for _, ok := range []string{"main", "feat/x", "v1.2", "a-b_c"} { | 204 | for _, ok := range []string{"main", "feat/x", "v1.2", "a-b_c"} { | ||
| 195 | got, err := domain.ParseBranchName(ok) | 205 | got, err := domain.ParseBranchName(ok) | ||
| 196 | require.NoError(t, err, ok) | 206 | require.NoError(t, err, ok) | ||
M internal/domain/name_test.go +6 -0
| @@ -14,6 +14,7 @@ import ( | |||
| 14 | 14 | ) | |
| 15 | 15 | ||
| 16 | 16 | func TestParseRepoName(t *testing.T) { | |
| 17 | + | t.Parallel() | |
| 17 | 18 | tests := []struct { | |
| 18 | 19 | in string | |
| 19 | 20 | valid bool | |
| @@ -45,6 +46,7 @@ func TestParseRepoName(t *testing.T) { | |||
| 45 | 46 | } | |
| 46 | 47 | for _, tt := range tests { | |
| 47 | 48 | t.Run(tt.in, func(t *testing.T) { | |
| 49 | + | t.Parallel() | |
| 48 | 50 | n, err := domain.ParseRepoName(tt.in) | |
| 49 | 51 | if tt.valid { | |
| 50 | 52 | require.NoError(t, err) | |
| @@ -66,6 +68,7 @@ func TestParseRepoName(t *testing.T) { | |||
| 66 | 68 | } | |
| 67 | 69 | ||
| 68 | 70 | func TestParseRepoPath(t *testing.T) { | |
| 71 | + | t.Parallel() | |
| 69 | 72 | tests := []struct { | |
| 70 | 73 | in string | |
| 71 | 74 | want string | |
| @@ -80,6 +83,7 @@ func TestParseRepoPath(t *testing.T) { | |||
| 80 | 83 | } | |
| 81 | 84 | for _, tt := range tests { | |
| 82 | 85 | t.Run(tt.in, func(t *testing.T) { | |
| 86 | + | t.Parallel() | |
| 83 | 87 | n, err := domain.ParseRepoPath(tt.in) | |
| 84 | 88 | if !tt.valid { | |
| 85 | 89 | assert.ErrorIs(t, err, domain.ErrInvalidName) | |
| @@ -92,6 +96,7 @@ func TestParseRepoPath(t *testing.T) { | |||
| 92 | 96 | } | |
| 93 | 97 | ||
| 94 | 98 | func TestParseVisibility(t *testing.T) { | |
| 99 | + | t.Parallel() | |
| 95 | 100 | for in, want := range map[string]domain.Visibility{"public": domain.VisibilityPublic, "private": domain.VisibilityPrivate} { | |
| 96 | 101 | got, err := domain.ParseVisibility(in) | |
| 97 | 102 | require.NoError(t, err) | |
| @@ -104,6 +109,7 @@ func TestParseVisibility(t *testing.T) { | |||
| 104 | 109 | } | |
| 105 | 110 | ||
| 106 | 111 | func TestPrincipal(t *testing.T) { | |
| 112 | + | t.Parallel() | |
| 107 | 113 | var zero domain.Principal | |
| 108 | 114 | assert.True(t, zero.IsAnonymous()) | |
| 109 | 115 | assert.False(t, zero.IsRoot()) | |
| @@ -14,6 +14,7 @@ import ( | |||||
| 14 | ) | 14 | ) | ||
| 15 | 15 | ||||
| 16 | func TestParseRepoName(t *testing.T) { | 16 | func TestParseRepoName(t *testing.T) { | ||
| 17 | + | t.Parallel() | |||
| 17 | tests := []struct { | 18 | tests := []struct { | ||
| 18 | in string | 19 | in string | ||
| 19 | valid bool | 20 | valid bool | ||
| @@ -45,6 +46,7 @@ func TestParseRepoName(t *testing.T) { | |||||
| 45 | } | 46 | } | ||
| 46 | for _, tt := range tests { | 47 | for _, tt := range tests { | ||
| 47 | t.Run(tt.in, func(t *testing.T) { | 48 | t.Run(tt.in, func(t *testing.T) { | ||
| 49 | + | t.Parallel() | |||
| 48 | n, err := domain.ParseRepoName(tt.in) | 50 | n, err := domain.ParseRepoName(tt.in) | ||
| 49 | if tt.valid { | 51 | if tt.valid { | ||
| 50 | require.NoError(t, err) | 52 | require.NoError(t, err) | ||
| @@ -66,6 +68,7 @@ func TestParseRepoName(t *testing.T) { | |||||
| 66 | } | 68 | } | ||
| 67 | 69 | ||||
| 68 | func TestParseRepoPath(t *testing.T) { | 70 | func TestParseRepoPath(t *testing.T) { | ||
| 71 | + | t.Parallel() | |||
| 69 | tests := []struct { | 72 | tests := []struct { | ||
| 70 | in string | 73 | in string | ||
| 71 | want string | 74 | want string | ||
| @@ -80,6 +83,7 @@ func TestParseRepoPath(t *testing.T) { | |||||
| 80 | } | 83 | } | ||
| 81 | for _, tt := range tests { | 84 | for _, tt := range tests { | ||
| 82 | t.Run(tt.in, func(t *testing.T) { | 85 | t.Run(tt.in, func(t *testing.T) { | ||
| 86 | + | t.Parallel() | |||
| 83 | n, err := domain.ParseRepoPath(tt.in) | 87 | n, err := domain.ParseRepoPath(tt.in) | ||
| 84 | if !tt.valid { | 88 | if !tt.valid { | ||
| 85 | assert.ErrorIs(t, err, domain.ErrInvalidName) | 89 | assert.ErrorIs(t, err, domain.ErrInvalidName) | ||
| @@ -92,6 +96,7 @@ func TestParseRepoPath(t *testing.T) { | |||||
| 92 | } | 96 | } | ||
| 93 | 97 | ||||
| 94 | func TestParseVisibility(t *testing.T) { | 98 | func TestParseVisibility(t *testing.T) { | ||
| 99 | + | t.Parallel() | |||
| 95 | for in, want := range map[string]domain.Visibility{"public": domain.VisibilityPublic, "private": domain.VisibilityPrivate} { | 100 | for in, want := range map[string]domain.Visibility{"public": domain.VisibilityPublic, "private": domain.VisibilityPrivate} { | ||
| 96 | got, err := domain.ParseVisibility(in) | 101 | got, err := domain.ParseVisibility(in) | ||
| 97 | require.NoError(t, err) | 102 | require.NoError(t, err) | ||
| @@ -104,6 +109,7 @@ func TestParseVisibility(t *testing.T) { | |||||
| 104 | } | 109 | } | ||
| 105 | 110 | ||||
| 106 | func TestPrincipal(t *testing.T) { | 111 | func TestPrincipal(t *testing.T) { | ||
| 112 | + | t.Parallel() | |||
| 107 | var zero domain.Principal | 113 | var zero domain.Principal | ||
| 108 | assert.True(t, zero.IsAnonymous()) | 114 | assert.True(t, zero.IsAnonymous()) | ||
| 109 | assert.False(t, zero.IsRoot()) | 115 | assert.False(t, zero.IsRoot()) | ||
M internal/domain/text_test.go +6 -0
| @@ -16,6 +16,7 @@ import ( | |||
| 16 | 16 | // textCases are the rules shared by every text; multi says whether line breaks | |
| 17 | 17 | // are allowed. | |
| 18 | 18 | func TestParseMRTitle(t *testing.T) { | |
| 19 | + | t.Parallel() | |
| 19 | 20 | tests := []struct { | |
| 20 | 21 | name string | |
| 21 | 22 | in string | |
| @@ -48,6 +49,7 @@ func TestParseMRTitle(t *testing.T) { | |||
| 48 | 49 | } | |
| 49 | 50 | for _, tc := range tests { | |
| 50 | 51 | t.Run(tc.name, func(t *testing.T) { | |
| 52 | + | t.Parallel() | |
| 51 | 53 | got, err := domain.ParseMRTitle(tc.in) | |
| 52 | 54 | if tc.reason == "" { | |
| 53 | 55 | require.NoError(t, err) | |
| @@ -64,6 +66,7 @@ func TestParseMRTitle(t *testing.T) { | |||
| 64 | 66 | } | |
| 65 | 67 | ||
| 66 | 68 | func TestParsePlainText(t *testing.T) { | |
| 69 | + | t.Parallel() | |
| 67 | 70 | tests := []struct { | |
| 68 | 71 | name string | |
| 69 | 72 | in string | |
| @@ -92,6 +95,7 @@ func TestParsePlainText(t *testing.T) { | |||
| 92 | 95 | } | |
| 93 | 96 | for _, tc := range tests { | |
| 94 | 97 | t.Run(tc.name, func(t *testing.T) { | |
| 98 | + | t.Parallel() | |
| 95 | 99 | got, err := domain.ParsePlainText(tc.in) | |
| 96 | 100 | if tc.reason == "" { | |
| 97 | 101 | require.NoError(t, err) | |
| @@ -107,6 +111,7 @@ func TestParsePlainText(t *testing.T) { | |||
| 107 | 111 | } | |
| 108 | 112 | ||
| 109 | 113 | func TestPlainTextIsBlank(t *testing.T) { | |
| 114 | + | t.Parallel() | |
| 110 | 115 | for in, blank := range map[string]bool{"": true, " \n\n ": true, "x": false, " x ": false} { | |
| 111 | 116 | got, err := domain.ParsePlainText(in) | |
| 112 | 117 | require.NoError(t, err) | |
| @@ -115,6 +120,7 @@ func TestPlainTextIsBlank(t *testing.T) { | |||
| 115 | 120 | } | |
| 116 | 121 | ||
| 117 | 122 | func TestViolations(t *testing.T) { | |
| 123 | + | t.Parallel() | |
| 118 | 124 | var v domain.Violations | |
| 119 | 125 | require.NoError(t, v.Err()) | |
| 120 | 126 | ||
| @@ -16,6 +16,7 @@ import ( | |||||
| 16 | // textCases are the rules shared by every text; multi says whether line breaks | 16 | // textCases are the rules shared by every text; multi says whether line breaks | ||
| 17 | // are allowed. | 17 | // are allowed. | ||
| 18 | func TestParseMRTitle(t *testing.T) { | 18 | func TestParseMRTitle(t *testing.T) { | ||
| 19 | + | t.Parallel() | |||
| 19 | tests := []struct { | 20 | tests := []struct { | ||
| 20 | name string | 21 | name string | ||
| 21 | in string | 22 | in string | ||
| @@ -48,6 +49,7 @@ func TestParseMRTitle(t *testing.T) { | |||||
| 48 | } | 49 | } | ||
| 49 | for _, tc := range tests { | 50 | for _, tc := range tests { | ||
| 50 | t.Run(tc.name, func(t *testing.T) { | 51 | t.Run(tc.name, func(t *testing.T) { | ||
| 52 | + | t.Parallel() | |||
| 51 | got, err := domain.ParseMRTitle(tc.in) | 53 | got, err := domain.ParseMRTitle(tc.in) | ||
| 52 | if tc.reason == "" { | 54 | if tc.reason == "" { | ||
| 53 | require.NoError(t, err) | 55 | require.NoError(t, err) | ||
| @@ -64,6 +66,7 @@ func TestParseMRTitle(t *testing.T) { | |||||
| 64 | } | 66 | } | ||
| 65 | 67 | ||||
| 66 | func TestParsePlainText(t *testing.T) { | 68 | func TestParsePlainText(t *testing.T) { | ||
| 69 | + | t.Parallel() | |||
| 67 | tests := []struct { | 70 | tests := []struct { | ||
| 68 | name string | 71 | name string | ||
| 69 | in string | 72 | in string | ||
| @@ -92,6 +95,7 @@ func TestParsePlainText(t *testing.T) { | |||||
| 92 | } | 95 | } | ||
| 93 | for _, tc := range tests { | 96 | for _, tc := range tests { | ||
| 94 | t.Run(tc.name, func(t *testing.T) { | 97 | t.Run(tc.name, func(t *testing.T) { | ||
| 98 | + | t.Parallel() | |||
| 95 | got, err := domain.ParsePlainText(tc.in) | 99 | got, err := domain.ParsePlainText(tc.in) | ||
| 96 | if tc.reason == "" { | 100 | if tc.reason == "" { | ||
| 97 | require.NoError(t, err) | 101 | require.NoError(t, err) | ||
| @@ -107,6 +111,7 @@ func TestParsePlainText(t *testing.T) { | |||||
| 107 | } | 111 | } | ||
| 108 | 112 | ||||
| 109 | func TestPlainTextIsBlank(t *testing.T) { | 113 | func TestPlainTextIsBlank(t *testing.T) { | ||
| 114 | + | t.Parallel() | |||
| 110 | for in, blank := range map[string]bool{"": true, " \n\n ": true, "x": false, " x ": false} { | 115 | for in, blank := range map[string]bool{"": true, " \n\n ": true, "x": false, " x ": false} { | ||
| 111 | got, err := domain.ParsePlainText(in) | 116 | got, err := domain.ParsePlainText(in) | ||
| 112 | require.NoError(t, err) | 117 | require.NoError(t, err) | ||
| @@ -115,6 +120,7 @@ func TestPlainTextIsBlank(t *testing.T) { | |||||
| 115 | } | 120 | } | ||
| 116 | 121 | ||||
| 117 | func TestViolations(t *testing.T) { | 122 | func TestViolations(t *testing.T) { | ||
| 123 | + | t.Parallel() | |||
| 118 | var v domain.Violations | 124 | var v domain.Violations | ||
| 119 | require.NoError(t, v.Err()) | 125 | require.NoError(t, v.Err()) | ||
| 120 | 126 | ||||
M internal/domain/token_test.go +6 -0
| @@ -13,6 +13,7 @@ import ( | |||
| 13 | 13 | ) | |
| 14 | 14 | ||
| 15 | 15 | func TestScopes(t *testing.T) { | |
| 16 | + | t.Parallel() | |
| 16 | 17 | all := domain.AllScopes() | |
| 17 | 18 | assert.Len(t, all, 9) | |
| 18 | 19 | for _, s := range all { | |
| @@ -27,6 +28,7 @@ func TestScopes(t *testing.T) { | |||
| 27 | 28 | } | |
| 28 | 29 | ||
| 29 | 30 | func TestHashTokenSecret(t *testing.T) { | |
| 31 | + | t.Parallel() | |
| 30 | 32 | h := domain.HashTokenSecret("ermite_abc") | |
| 31 | 33 | assert.Equal(t, h, domain.HashTokenSecret("ermite_abc")) | |
| 32 | 34 | assert.NotEqual(t, h, domain.HashTokenSecret("ermite_abd")) | |
| @@ -36,6 +38,7 @@ func TestHashTokenSecret(t *testing.T) { | |||
| 36 | 38 | } | |
| 37 | 39 | ||
| 38 | 40 | func TestTokenExpired(t *testing.T) { | |
| 41 | + | t.Parallel() | |
| 39 | 42 | now := time.Date(2026, 10, 10, 8, 0, 0, 0, time.UTC) | |
| 40 | 43 | assert.False(t, domain.Token{}.Expired(now), "no expiry never expires") | |
| 41 | 44 | assert.False(t, domain.Token{ExpiresAt: now.Add(time.Second)}.Expired(now)) | |
| @@ -44,6 +47,7 @@ func TestTokenExpired(t *testing.T) { | |||
| 44 | 47 | } | |
| 45 | 48 | ||
| 46 | 49 | func TestPrincipalScopes(t *testing.T) { | |
| 50 | + | t.Parallel() | |
| 47 | 51 | a, b := mustName(t, "a"), mustName(t, "b") | |
| 48 | 52 | tok := domain.Token{ID: "t1", Scopes: []domain.Scope{domain.ScopeRepoRead, domain.ScopeRepoWrite}, Repos: []domain.RepoName{a}} | |
| 49 | 53 | p := domain.TokenPrincipal(tok) | |
| @@ -84,6 +88,7 @@ func mustName(t *testing.T, s string) domain.RepoName { | |||
| 84 | 88 | } | |
| 85 | 89 | ||
| 86 | 90 | func TestOwnerPrincipal(t *testing.T) { | |
| 91 | + | t.Parallel() | |
| 87 | 92 | o := domain.Owner() | |
| 88 | 93 | assert.Equal(t, "owner", o.Label()) | |
| 89 | 94 | assert.True(t, o.IsOwner()) | |
| @@ -96,6 +101,7 @@ func TestOwnerPrincipal(t *testing.T) { | |||
| 96 | 101 | } | |
| 97 | 102 | ||
| 98 | 103 | func TestHasOwnerRights(t *testing.T) { | |
| 104 | + | t.Parallel() | |
| 99 | 105 | a := mustName(t, "a") | |
| 100 | 106 | all := domain.AllScopes() | |
| 101 | 107 | assert.True(t, domain.Root().HasOwnerRights()) | |
| @@ -13,6 +13,7 @@ import ( | |||||
| 13 | ) | 13 | ) | ||
| 14 | 14 | ||||
| 15 | func TestScopes(t *testing.T) { | 15 | func TestScopes(t *testing.T) { | ||
| 16 | + | t.Parallel() | |||
| 16 | all := domain.AllScopes() | 17 | all := domain.AllScopes() | ||
| 17 | assert.Len(t, all, 9) | 18 | assert.Len(t, all, 9) | ||
| 18 | for _, s := range all { | 19 | for _, s := range all { | ||
| @@ -27,6 +28,7 @@ func TestScopes(t *testing.T) { | |||||
| 27 | } | 28 | } | ||
| 28 | 29 | ||||
| 29 | func TestHashTokenSecret(t *testing.T) { | 30 | func TestHashTokenSecret(t *testing.T) { | ||
| 31 | + | t.Parallel() | |||
| 30 | h := domain.HashTokenSecret("ermite_abc") | 32 | h := domain.HashTokenSecret("ermite_abc") | ||
| 31 | assert.Equal(t, h, domain.HashTokenSecret("ermite_abc")) | 33 | assert.Equal(t, h, domain.HashTokenSecret("ermite_abc")) | ||
| 32 | assert.NotEqual(t, h, domain.HashTokenSecret("ermite_abd")) | 34 | assert.NotEqual(t, h, domain.HashTokenSecret("ermite_abd")) | ||
| @@ -36,6 +38,7 @@ func TestHashTokenSecret(t *testing.T) { | |||||
| 36 | } | 38 | } | ||
| 37 | 39 | ||||
| 38 | func TestTokenExpired(t *testing.T) { | 40 | func TestTokenExpired(t *testing.T) { | ||
| 41 | + | t.Parallel() | |||
| 39 | now := time.Date(2026, 10, 10, 8, 0, 0, 0, time.UTC) | 42 | now := time.Date(2026, 10, 10, 8, 0, 0, 0, time.UTC) | ||
| 40 | assert.False(t, domain.Token{}.Expired(now), "no expiry never expires") | 43 | assert.False(t, domain.Token{}.Expired(now), "no expiry never expires") | ||
| 41 | assert.False(t, domain.Token{ExpiresAt: now.Add(time.Second)}.Expired(now)) | 44 | assert.False(t, domain.Token{ExpiresAt: now.Add(time.Second)}.Expired(now)) | ||
| @@ -44,6 +47,7 @@ func TestTokenExpired(t *testing.T) { | |||||
| 44 | } | 47 | } | ||
| 45 | 48 | ||||
| 46 | func TestPrincipalScopes(t *testing.T) { | 49 | func TestPrincipalScopes(t *testing.T) { | ||
| 50 | + | t.Parallel() | |||
| 47 | a, b := mustName(t, "a"), mustName(t, "b") | 51 | a, b := mustName(t, "a"), mustName(t, "b") | ||
| 48 | tok := domain.Token{ID: "t1", Scopes: []domain.Scope{domain.ScopeRepoRead, domain.ScopeRepoWrite}, Repos: []domain.RepoName{a}} | 52 | tok := domain.Token{ID: "t1", Scopes: []domain.Scope{domain.ScopeRepoRead, domain.ScopeRepoWrite}, Repos: []domain.RepoName{a}} | ||
| 49 | p := domain.TokenPrincipal(tok) | 53 | p := domain.TokenPrincipal(tok) | ||
| @@ -84,6 +88,7 @@ func mustName(t *testing.T, s string) domain.RepoName { | |||||
| 84 | } | 88 | } | ||
| 85 | 89 | ||||
| 86 | func TestOwnerPrincipal(t *testing.T) { | 90 | func TestOwnerPrincipal(t *testing.T) { | ||
| 91 | + | t.Parallel() | |||
| 87 | o := domain.Owner() | 92 | o := domain.Owner() | ||
| 88 | assert.Equal(t, "owner", o.Label()) | 93 | assert.Equal(t, "owner", o.Label()) | ||
| 89 | assert.True(t, o.IsOwner()) | 94 | assert.True(t, o.IsOwner()) | ||
| @@ -96,6 +101,7 @@ func TestOwnerPrincipal(t *testing.T) { | |||||
| 96 | } | 101 | } | ||
| 97 | 102 | ||||
| 98 | func TestHasOwnerRights(t *testing.T) { | 103 | func TestHasOwnerRights(t *testing.T) { | ||
| 104 | + | t.Parallel() | |||
| 99 | a := mustName(t, "a") | 105 | a := mustName(t, "a") | ||
| 100 | all := domain.AllScopes() | 106 | all := domain.AllScopes() | ||
| 101 | assert.True(t, domain.Root().HasOwnerRights()) | 107 | assert.True(t, domain.Root().HasOwnerRights()) | ||