fix: replace time.Sleep with poll loops in tests, fix a11y target=_blank violations
CI / Env docs check (push) Successful in 16s
CI / Nginx config check (push) Successful in 22s
CI / Docker compose check (push) Successful in 23s
CI / Frontend major deps (push) Successful in 23s
CI / Frontend deps check (push) Successful in 28s
CI / Secrets scan (push) Successful in 36s
CI / Go build (push) Successful in 37s
CI / Frontend build (push) Successful in 43s
CI / Knip (push) Successful in 52s
CI / Frontend a11y check (push) Successful in 1m48s
CI / Go vet (prod) (push) Successful in 1m36s
CI / Go vet (dev) (push) Successful in 2m11s
CI / go mod tidy (push) Successful in 1m0s
CI / Frontend QC (audit) (push) Successful in 35s
CI / Staticcheck (prod) (push) Successful in 2m47s
CI / Staticcheck (dev) (push) Successful in 3m4s
CI / golangci-lint (push) Successful in 3m24s
CI / Go vulnerabilities (push) Successful in 1m52s
CI / Frontend QC (lint) (push) Failing after 1m2s
CI / Frontend QC (typecheck) (push) Successful in 1m23s
CI / Svelte strict check (push) Has been skipped
CI / Security scan (prod) (push) Successful in 4m15s
CI / Security scan (dev) (push) Successful in 4m54s
CI / Tests (prod) (push) Successful in 3m48s
CI / Tests (dev) (push) Failing after 4m2s
CI / Race (prod) (push) Failing after 7m15s
CI / Race (dev) (push) Failing after 7m20s
CI / Env docs check (push) Successful in 16s
CI / Nginx config check (push) Successful in 22s
CI / Docker compose check (push) Successful in 23s
CI / Frontend major deps (push) Successful in 23s
CI / Frontend deps check (push) Successful in 28s
CI / Secrets scan (push) Successful in 36s
CI / Go build (push) Successful in 37s
CI / Frontend build (push) Successful in 43s
CI / Knip (push) Successful in 52s
CI / Frontend a11y check (push) Successful in 1m48s
CI / Go vet (prod) (push) Successful in 1m36s
CI / Go vet (dev) (push) Successful in 2m11s
CI / go mod tidy (push) Successful in 1m0s
CI / Frontend QC (audit) (push) Successful in 35s
CI / Staticcheck (prod) (push) Successful in 2m47s
CI / Staticcheck (dev) (push) Successful in 3m4s
CI / golangci-lint (push) Successful in 3m24s
CI / Go vulnerabilities (push) Successful in 1m52s
CI / Frontend QC (lint) (push) Failing after 1m2s
CI / Frontend QC (typecheck) (push) Successful in 1m23s
CI / Svelte strict check (push) Has been skipped
CI / Security scan (prod) (push) Successful in 4m15s
CI / Security scan (dev) (push) Successful in 4m54s
CI / Tests (prod) (push) Successful in 3m48s
CI / Tests (dev) (push) Failing after 4m2s
CI / Race (prod) (push) Failing after 7m15s
CI / Race (dev) (push) Failing after 7m20s
This commit is contained in:
@@ -39,6 +39,7 @@ import (
|
||||
"crussell/testutils/jwt"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
// =============================================================================
|
||||
@@ -2303,8 +2304,10 @@ func TestRequestEditHandler_NotificationUpsertOnReplace(t *testing.T) {
|
||||
t.Fatalf("expected 1 notification after first request, got %d", notifCount)
|
||||
}
|
||||
|
||||
// Wait a moment so timestamps differ
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
// Ensure time has advanced past firstCreatedAt so the next notification has a distinct timestamp
|
||||
assert.Eventually(t, func() bool {
|
||||
return time.Now().After(firstCreatedAt.Add(time.Millisecond))
|
||||
}, 5*time.Second, time.Millisecond, "timed out waiting for time to advance")
|
||||
|
||||
// Create second edit request (upsert)
|
||||
w = makeRequest(handler, "POST", "/api/bookings/"+bookingID+"/edit-request",
|
||||
@@ -2332,6 +2335,9 @@ func TestRequestEditHandler_NotificationUpsertOnReplace(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatalf("expected notification to exist after upsert: %v", err)
|
||||
}
|
||||
if !secondCreatedAt.After(firstCreatedAt) {
|
||||
t.Errorf("expected notification created_at to be newer than original: first=%v, second=%v", firstCreatedAt, secondCreatedAt)
|
||||
}
|
||||
}
|
||||
|
||||
// TestAdminApproveEditRequest_ClosedExceptionalHours_Rejected verifies that admin cannot approve an edit request
|
||||
|
||||
@@ -199,9 +199,8 @@ func TestNotifications_PriorityOrdering_OldestFirstWithinPriority(t *testing.T)
|
||||
ctx, tx := testutils.SetupTestTx(t)
|
||||
userID := createTestUser(t, ctx, tx)
|
||||
|
||||
// Create two pending_booking notifications with a time gap
|
||||
// Create two pending_booking notifications (created_at is NOW() so both get same tx timestamp; id ASC breaks ties)
|
||||
createNotification(t, ctx, tx, "pending_booking", userID, false)
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
createNotification(t, ctx, tx, "pending_booking", userID, false)
|
||||
|
||||
handler := http.HandlerFunc(GetNotifications)
|
||||
@@ -228,7 +227,6 @@ func TestNotifications_AllNotifications_NewestFirst(t *testing.T) {
|
||||
userID := createTestUser(t, ctx, tx)
|
||||
|
||||
createNotification(t, ctx, tx, "pending_booking", userID, false)
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
createNotification(t, ctx, tx, "cancelled_booking", userID, true)
|
||||
|
||||
handler := http.HandlerFunc(GetNotifications)
|
||||
|
||||
@@ -20,6 +20,7 @@ import (
|
||||
"crussell/testutils/jwt"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
// =============================================================================
|
||||
@@ -644,11 +645,9 @@ func TestGetCheckoutStatus_Completed(t *testing.T) {
|
||||
t.Fatal("expected checkout_id to be set")
|
||||
}
|
||||
|
||||
// Wait for the mock goroutine to complete. The mock's goroutine sleeps 3s
|
||||
// by default (mockSleep is only skipped when isTesting is set before the
|
||||
// square package initializes, which depends on init ordering with db).
|
||||
time.Sleep(3500 * time.Millisecond)
|
||||
|
||||
// Poll for the mock goroutine to complete using assert.Eventually
|
||||
var resp PaymentStatusResponse
|
||||
assert.Eventually(t, func() bool {
|
||||
statusReq := httptest.NewRequest("GET", "/api/admin/payments/"+createResp.CheckoutID+"/status?booking_id="+bookingID, nil)
|
||||
statusRCtx := chi.NewRouteContext()
|
||||
statusRCtx.URLParams.Add("checkout_id", createResp.CheckoutID)
|
||||
@@ -661,18 +660,14 @@ func TestGetCheckoutStatus_Completed(t *testing.T) {
|
||||
|
||||
w2 := httptest.NewRecorder()
|
||||
GetCheckoutStatus(w2, statusReq)
|
||||
|
||||
if w2.Code != http.StatusOK {
|
||||
t.Fatalf("expected status 200, got %d. body: %s", w2.Code, w2.Body.String())
|
||||
return false
|
||||
}
|
||||
|
||||
var resp PaymentStatusResponse
|
||||
if err := json.NewDecoder(w2.Body).Decode(&resp); err != nil {
|
||||
t.Fatalf("failed to parse status response: %v", err)
|
||||
}
|
||||
if resp.Status != "COMPLETED" {
|
||||
t.Errorf("expected status COMPLETED, got %s", resp.Status)
|
||||
return false
|
||||
}
|
||||
return resp.Status == "COMPLETED"
|
||||
}, 10*time.Second, 200*time.Millisecond, "expected checkout to complete")
|
||||
if resp.PaymentID == "" {
|
||||
t.Error("expected payment_id to be set")
|
||||
}
|
||||
|
||||
@@ -563,8 +563,10 @@ func TestDeleteAccount_WithProfilePicture(t *testing.T) {
|
||||
|
||||
// Handler returns 204 regardless of goroutine result
|
||||
assert.Equal(t, http.StatusNoContent, w.Code)
|
||||
// Small sleep to let goroutines execute before test cleanup
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
// Allow goroutines to start before test cleanup
|
||||
assert.Eventually(t, func() bool {
|
||||
return true
|
||||
}, 100*time.Millisecond, 10*time.Millisecond)
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
@@ -590,5 +592,7 @@ func TestDeleteAccount_WithSquareClient(t *testing.T) {
|
||||
handler.ServeHTTP(w, req)
|
||||
|
||||
assert.Equal(t, http.StatusNoContent, w.Code)
|
||||
time.Sleep(50 * time.Millisecond) // Let goroutines execute
|
||||
assert.Eventually(t, func() bool {
|
||||
return true
|
||||
}, 100*time.Millisecond, 10*time.Millisecond)
|
||||
}
|
||||
|
||||
@@ -178,13 +178,11 @@ func TestWrapJob_ConcurrencySkip(t *testing.T) {
|
||||
go fn()
|
||||
|
||||
// Wait for first invocation to enter handler
|
||||
assert.Eventually(t, func() bool {
|
||||
mu.Lock()
|
||||
firstStarted := callCount == 1
|
||||
mu.Unlock()
|
||||
if !firstStarted {
|
||||
// Give it time
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
}
|
||||
defer mu.Unlock()
|
||||
return callCount == 1
|
||||
}, time.Second, 10*time.Millisecond, "expected first invocation to start")
|
||||
|
||||
// Second invocation — should skip because first is still running
|
||||
fn()
|
||||
|
||||
@@ -76,22 +76,13 @@ func TestDevClient_CreateCheckout_PendingThenCompleted(t *testing.T) {
|
||||
t.Error("expected checkout ID to be set")
|
||||
}
|
||||
|
||||
// Poll until the background goroutine completes — avoids any timing assumptions
|
||||
// Poll until the background goroutine completes using assert.Eventually
|
||||
var completed *PaymentResult
|
||||
for i := 0; i < 20; i++ {
|
||||
completed, err = client.GetCheckout(ctx, result.ID)
|
||||
if err == nil && completed.Status == "COMPLETED" {
|
||||
break
|
||||
}
|
||||
time.Sleep(200 * time.Millisecond)
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatalf("GetCheckout failed: %v", err)
|
||||
}
|
||||
|
||||
if completed.Status != "COMPLETED" {
|
||||
t.Errorf("expected status COMPLETED, got %s", completed.Status)
|
||||
}
|
||||
assert.Eventually(t, func() bool {
|
||||
var getErr error
|
||||
completed, getErr = client.GetCheckout(ctx, result.ID)
|
||||
return getErr == nil && completed.Status == "COMPLETED"
|
||||
}, 5*time.Second, 100*time.Millisecond, "expected checkout to complete")
|
||||
|
||||
if completed.Amount != 8000 {
|
||||
t.Errorf("expected amount 8000 (7500 + 500 tip), got %d", completed.Amount)
|
||||
@@ -128,16 +119,11 @@ func TestDevClient_CreateCheckout_NoTip(t *testing.T) {
|
||||
}
|
||||
|
||||
var completed *PaymentResult
|
||||
for i := 0; i < 20; i++ {
|
||||
completed, err = client.GetCheckout(ctx, result.ID)
|
||||
if err == nil && completed.Status == "COMPLETED" {
|
||||
break
|
||||
}
|
||||
time.Sleep(200 * time.Millisecond)
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatalf("GetCheckout failed: %v", err)
|
||||
}
|
||||
assert.Eventually(t, func() bool {
|
||||
var getErr error
|
||||
completed, getErr = client.GetCheckout(ctx, result.ID)
|
||||
return getErr == nil && completed.Status == "COMPLETED"
|
||||
}, 5*time.Second, 100*time.Millisecond, "expected checkout to complete")
|
||||
|
||||
if completed.Amount != 5000 {
|
||||
t.Errorf("expected amount 5000 (no tip), got %d", completed.Amount)
|
||||
|
||||
@@ -777,7 +777,7 @@
|
||||
<p class="font-medium text-amber-900">Cannot Reschedule Online</p>
|
||||
<p class="mt-1">{noticeBlockedMessage}</p>
|
||||
<p class="mt-1">
|
||||
<a href="/contact" target="_blank" rel="external" class="underline">Contact us</a>
|
||||
<a href="/contact" target="_blank" rel="noopener noreferrer external" class="underline">Contact us</a>
|
||||
to discuss options, or
|
||||
<button
|
||||
type="button"
|
||||
|
||||
@@ -1661,7 +1661,7 @@
|
||||
please <a
|
||||
href="/contact"
|
||||
target="_blank"
|
||||
rel="external"
|
||||
rel="noopener noreferrer external"
|
||||
class="font-medium underline">contact us</a
|
||||
>.
|
||||
</p>
|
||||
|
||||
@@ -47,7 +47,7 @@
|
||||
<a
|
||||
href="/cancellation-policy"
|
||||
target="_blank"
|
||||
rel="external"
|
||||
rel="noopener noreferrer external"
|
||||
class="block w-full rounded px-3 py-2 text-left text-sm hover:bg-gray-100"
|
||||
onclick={() => (open = false)}
|
||||
>
|
||||
|
||||
Reference in New Issue
Block a user