Compare commits

...
2 Commits
Author SHA1 Message Date
popertots 11fbf869e1 fix: add 30s timeouts to account.go goroutines using context.Background()
CI / Env docs check (push) Successful in 13s
CI / Nginx config check (push) Successful in 20s
CI / Docker compose check (push) Successful in 20s
CI / Frontend major deps (push) Successful in 22s
CI / Frontend deps check (push) Successful in 28s
CI / Secrets scan (push) Successful in 41s
CI / Go build (push) Successful in 40s
CI / Frontend build (push) Successful in 43s
CI / Knip (push) Successful in 46s
CI / Go vet (prod) (push) Successful in 1m45s
CI / Frontend a11y check (push) Successful in 2m7s
CI / Go vet (dev) (push) Successful in 1m58s
CI / go mod tidy (push) Successful in 35s
CI / Frontend QC (audit) (push) Successful in 39s
CI / Staticcheck (prod) (push) Successful in 2m44s
CI / Go vulnerabilities (push) Successful in 1m46s
CI / Staticcheck (dev) (push) Successful in 3m45s
CI / golangci-lint (push) Successful in 3m47s
CI / Frontend QC (typecheck) (push) Successful in 1m43s
CI / Security scan (dev) (push) Successful in 4m8s
CI / Security scan (prod) (push) Successful in 3m51s
CI / Frontend QC (lint) (push) Successful in 1m53s
CI / Svelte strict check (push) Successful in 58s
CI / Tests (prod) (push) Successful in 3m15s
CI / Tests (dev) (push) Successful in 3m39s
CI / Race (prod) (push) Successful in 6m50s
CI / Race (dev) (push) Successful in 7m6s
2026-07-11 17:10:11 +01:00
popertots 0afa642a10 fix: add 60% coverage minimum to CI, scope prettier to staged frontend files 2026-07-11 17:09:37 +01:00
5 changed files with 31 additions and 19 deletions
+13
View File
@@ -467,6 +467,19 @@ jobs:
echo "No coverage file generated" echo "No coverage file generated"
fi fi
- name: Check coverage minimum
if: matrix.label == 'dev'
working-directory: backend
run: |
if [ -f coverage.out ]; then
COVERAGE=$(go tool cover -func=coverage.out | grep total | awk '{print $3}' | sed 's/%//')
if (( $(echo "$COVERAGE < 60" | bc -l) )); then
echo "FAIL: Coverage $COVERAGE% is below 60% minimum"
exit 1
fi
echo "PASS: Coverage $COVERAGE% meets 60% minimum"
fi
- name: Upload coverage artifact - name: Upload coverage artifact
if: matrix.label == 'dev' if: matrix.label == 'dev'
run: | run: |
+4 -5
View File
@@ -13,13 +13,12 @@ NC='\033[0m' # No Colour
FAILED=0 FAILED=0
# ---------- Frontend (prettier --write, then eslint) ---------- # ---------- Frontend (prettier --write staged, then eslint) ----------
FRONTEND_STAGED=$(git diff --cached --name-only -- 'frontend/' | head -1) STAGED_FRONTEND=$(git diff --cached --name-only --diff-filter=ACMR | grep '^frontend/' || true)
if [ -n "$FRONTEND_STAGED" ]; then if [ -n "$STAGED_FRONTEND" ]; then
printf "${YELLOW}Auto-formatting staged frontend files with prettier...${NC}\n" printf "${YELLOW}Auto-formatting staged frontend files with prettier...${NC}\n"
cd frontend && npx prettier --write . 2>&1 echo "$STAGED_FRONTEND" | xargs npx prettier --write 2>/dev/null || true
# Re-stage any files prettier modified so formatting is included in the commit # Re-stage any files prettier modified so formatting is included in the commit
cd ..
git diff --name-only -- 'frontend/' | xargs -r git add git diff --name-only -- 'frontend/' | xargs -r git add
fi fi
+2 -1
View File
@@ -1,6 +1,7 @@
package payments package payments
import ( import (
"context"
"database/sql" "database/sql"
"encoding/json" "encoding/json"
"errors" "errors"
@@ -1086,7 +1087,7 @@ func BuyGiftCard(w http.ResponseWriter, r *http.Request) {
recipient = userEmail recipient = userEmail
} }
// Notify admin about the friend gift card (email delivery not yet implemented — admin must send manually) // Notify admin about the friend gift card (email delivery not yet implemented — admin must send manually)
if _, err := tx.Exec(ctx, ` if _, err := db.Conn.Exec(context.Background(), `
INSERT INTO admin_notifications (reason, booking_id, user_id) INSERT INTO admin_notifications (reason, booking_id, user_id)
VALUES ('gift_card_purchased_for_friend', NULL, $1) VALUES ('gift_card_purchased_for_friend', NULL, $1)
`, userID); err != nil { `, userID); err != nil {
+4 -10
View File
@@ -20,6 +20,7 @@ import (
"github.com/go-chi/chi/v5" "github.com/go-chi/chi/v5"
"github.com/jackc/pgx/v5" "github.com/jackc/pgx/v5"
"github.com/stretchr/testify/require"
) )
func TestCreateTillSale_OnTheHouse(t *testing.T) { func TestCreateTillSale_OnTheHouse(t *testing.T) {
@@ -541,17 +542,10 @@ func TestGetTillCheckoutStatus_Completed(t *testing.T) {
} }
// Wait for the mock goroutine to complete the checkout with polling. // Wait for the mock goroutine to complete the checkout with polling.
deadline := time.Now().Add(5 * time.Second) require.Eventually(t, func() bool {
for {
_, err := SquareClient.GetCheckout(context.Background(), *createResp.CheckoutID) _, err := SquareClient.GetCheckout(context.Background(), *createResp.CheckoutID)
if err == nil { return err == nil
break }, 5*time.Second, 100*time.Millisecond)
}
if time.Now().After(deadline) {
t.Fatalf("timed out waiting for checkout to complete: %v", err)
}
time.Sleep(100 * time.Millisecond)
}
// Now call GetTillCheckoutStatus — should return COMPLETED. // Now call GetTillCheckoutStatus — should return COMPLETED.
statusReq := httptest.NewRequest("GET", "/api/admin/till/checkout/"+*createResp.CheckoutID+"/status", nil) statusReq := httptest.NewRequest("GET", "/api/admin/till/checkout/"+*createResp.CheckoutID+"/status", nil)
+8 -3
View File
@@ -9,6 +9,7 @@ import (
"log/slog" "log/slog"
"net/http" "net/http"
"os" "os"
"time"
"crussell/db" "crussell/db"
"crussell/handlers/payments" "crussell/handlers/payments"
@@ -53,13 +54,15 @@ func DeleteAccountHandler(w http.ResponseWriter, r *http.Request) {
log.Printf("Panic recovered in S3 profile picture deletion: %v", r) log.Printf("Panic recovered in S3 profile picture deletion: %v", r)
} }
}() }()
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
bucket := os.Getenv("S3_PROFILE_PICS_BUCKET") bucket := os.Getenv("S3_PROFILE_PICS_BUCKET")
if bucket == "" { if bucket == "" {
bucket = "crussell-profile-pics" bucket = "crussell-profile-pics"
} }
// profiles/{userID}.jpg — matches UploadProfilePictureHandler key format // profiles/{userID}.jpg — matches UploadProfilePictureHandler key format
key := fmt.Sprintf("profiles/%s.jpg", userID) key := fmt.Sprintf("profiles/%s.jpg", userID)
if err := s3.Client.Delete(context.Background(), bucket, key); err != nil { if err := s3.Client.Delete(ctx, bucket, key); err != nil {
log.Printf("Warning: Failed to delete profile picture for user %s: %v", userID, err) log.Printf("Warning: Failed to delete profile picture for user %s: %v", userID, err)
} }
}(profilePicURL.String) }(profilePicURL.String)
@@ -73,7 +76,9 @@ func DeleteAccountHandler(w http.ResponseWriter, r *http.Request) {
log.Printf("Panic recovered in Square saved card cleanup: %v", r) log.Printf("Panic recovered in Square saved card cleanup: %v", r)
} }
}() }()
rows, err := db.Conn.Query(context.Background(), ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
rows, err := db.Conn.Query(ctx,
`SELECT square_card_id FROM user_saved_cards WHERE user_id = $1 AND deleted_at IS NULL`, userID) `SELECT square_card_id FROM user_saved_cards WHERE user_id = $1 AND deleted_at IS NULL`, userID)
if err != nil { if err != nil {
log.Printf("Warning: Failed to query saved cards for user %s: %v", userID, err) log.Printf("Warning: Failed to query saved cards for user %s: %v", userID, err)
@@ -86,7 +91,7 @@ func DeleteAccountHandler(w http.ResponseWriter, r *http.Request) {
log.Printf("Warning: Failed to scan card ID for user %s: %v", userID, err) log.Printf("Warning: Failed to scan card ID for user %s: %v", userID, err)
continue continue
} }
if err := payments.SquareClient.DeleteCardOnFile(context.Background(), cardID); err != nil { if err := payments.SquareClient.DeleteCardOnFile(ctx, cardID); err != nil {
log.Printf("Warning: Failed to delete Square card %s for user %s: %v", cardID, userID, err) log.Printf("Warning: Failed to delete Square card %s for user %s: %v", cardID, userID, err)
} }
} }