Compare commits
2
Commits
7d6cb0c375
...
11fbf869e1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
11fbf869e1 | ||
|
|
0afa642a10 |
@@ -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: |
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user