forked from cartsnitch/cartsnitch
Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a55c64a9c8 | |||
| 3a67b26e1f | |||
| 271406de9e | |||
| d0b855b45d | |||
| 14e17c5fc6 | |||
| 70b0801228 | |||
| a53daddb9a | |||
| 3351d74058 |
+8
-112
@@ -13,7 +13,6 @@ concurrency:
|
|||||||
permissions:
|
permissions:
|
||||||
contents: write
|
contents: write
|
||||||
packages: write
|
packages: write
|
||||||
security-events: write
|
|
||||||
|
|
||||||
env:
|
env:
|
||||||
REGISTRY: ghcr.io
|
REGISTRY: ghcr.io
|
||||||
@@ -152,42 +151,17 @@ jobs:
|
|||||||
type=raw,value=${{ steps.calver.outputs.version }},enable=${{ github.ref == 'refs/heads/main' }}
|
type=raw,value=${{ steps.calver.outputs.version }},enable=${{ github.ref == 'refs/heads/main' }}
|
||||||
type=raw,value=latest,enable=${{ github.ref == 'refs/heads/main' }}
|
type=raw,value=latest,enable=${{ github.ref == 'refs/heads/main' }}
|
||||||
|
|
||||||
- name: Build Docker image
|
- name: Build and push Docker image
|
||||||
uses: docker/build-push-action@v6
|
uses: docker/build-push-action@v6
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
load: true
|
push: ${{ github.event_name == 'push' }}
|
||||||
tags: ${{ steps.meta.outputs.tags }}
|
tags: ${{ steps.meta.outputs.tags }}
|
||||||
labels: ${{ steps.meta.outputs.labels }}
|
labels: ${{ steps.meta.outputs.labels }}
|
||||||
target: prod
|
target: prod
|
||||||
cache-from: type=gha
|
cache-from: type=gha
|
||||||
cache-to: type=gha,mode=max
|
cache-to: type=gha,mode=max
|
||||||
|
|
||||||
- name: Run Trivy vulnerability scanner
|
|
||||||
uses: aquasecurity/trivy-action@0.28.0
|
|
||||||
with:
|
|
||||||
image-ref: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:sha-${{ github.sha }}
|
|
||||||
format: 'sarif'
|
|
||||||
output: 'trivy-results-frontend.sarif'
|
|
||||||
severity: 'CRITICAL,HIGH'
|
|
||||||
exit-code: '1'
|
|
||||||
|
|
||||||
- name: Upload Trivy scan results to GitHub Security
|
|
||||||
uses: github/codeql-action/upload-sarif@v3
|
|
||||||
if: always()
|
|
||||||
with:
|
|
||||||
sarif_file: 'trivy-results-frontend.sarif'
|
|
||||||
|
|
||||||
- name: Push Docker image
|
|
||||||
if: github.event_name == 'push'
|
|
||||||
uses: docker/build-push-action@v6
|
|
||||||
with:
|
|
||||||
context: .
|
|
||||||
push: true
|
|
||||||
tags: ${{ steps.meta.outputs.tags }}
|
|
||||||
labels: ${{ steps.meta.outputs.labels }}
|
|
||||||
cache-from: type=gha
|
|
||||||
|
|
||||||
- name: Create git tag
|
- name: Create git tag
|
||||||
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
|
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
|
||||||
run: |
|
run: |
|
||||||
@@ -247,41 +221,15 @@ jobs:
|
|||||||
type=raw,value=${{ steps.calver.outputs.version }},enable=${{ github.ref == 'refs/heads/main' }}
|
type=raw,value=${{ steps.calver.outputs.version }},enable=${{ github.ref == 'refs/heads/main' }}
|
||||||
type=raw,value=latest,enable=${{ github.ref == 'refs/heads/main' }}
|
type=raw,value=latest,enable=${{ github.ref == 'refs/heads/main' }}
|
||||||
|
|
||||||
- name: Build Docker image
|
- name: Build and push auth Docker image
|
||||||
uses: docker/build-push-action@v6
|
uses: docker/build-push-action@v6
|
||||||
with:
|
with:
|
||||||
context: ./auth
|
context: ./auth
|
||||||
file: ./auth/Dockerfile
|
file: ./auth/Dockerfile
|
||||||
load: true
|
push: ${{ github.event_name == 'push' }}
|
||||||
tags: ${{ steps.meta.outputs.tags }}
|
tags: ${{ steps.meta.outputs.tags }}
|
||||||
labels: ${{ steps.meta.outputs.labels }}
|
labels: ${{ steps.meta.outputs.labels }}
|
||||||
|
|
||||||
- name: Run Trivy vulnerability scanner
|
|
||||||
uses: aquasecurity/trivy-action@0.28.0
|
|
||||||
with:
|
|
||||||
image-ref: ${{ env.REGISTRY }}/${{ env.AUTH_IMAGE_NAME }}:sha-${{ github.sha }}
|
|
||||||
format: 'sarif'
|
|
||||||
output: 'trivy-results-auth.sarif'
|
|
||||||
severity: 'CRITICAL,HIGH'
|
|
||||||
exit-code: '1'
|
|
||||||
|
|
||||||
- name: Upload Trivy scan results to GitHub Security
|
|
||||||
uses: github/codeql-action/upload-sarif@v3
|
|
||||||
if: always()
|
|
||||||
with:
|
|
||||||
sarif_file: 'trivy-results-auth.sarif'
|
|
||||||
|
|
||||||
- name: Push Docker image
|
|
||||||
if: github.event_name == 'push'
|
|
||||||
uses: docker/build-push-action@v6
|
|
||||||
with:
|
|
||||||
context: ./auth
|
|
||||||
file: ./auth/Dockerfile
|
|
||||||
push: true
|
|
||||||
tags: ${{ steps.meta.outputs.tags }}
|
|
||||||
labels: ${{ steps.meta.outputs.labels }}
|
|
||||||
cache-from: type=gha
|
|
||||||
|
|
||||||
build-and-push-receiptwitness:
|
build-and-push-receiptwitness:
|
||||||
runs-on: runners-cartsnitch
|
runs-on: runners-cartsnitch
|
||||||
if: github.event_name == 'push'
|
if: github.event_name == 'push'
|
||||||
@@ -330,41 +278,15 @@ jobs:
|
|||||||
type=raw,value=${{ steps.calver.outputs.version }},enable=${{ github.ref == 'refs/heads/main' }}
|
type=raw,value=${{ steps.calver.outputs.version }},enable=${{ github.ref == 'refs/heads/main' }}
|
||||||
type=raw,value=latest,enable=${{ github.ref == 'refs/heads/main' }}
|
type=raw,value=latest,enable=${{ github.ref == 'refs/heads/main' }}
|
||||||
|
|
||||||
- name: Build Docker image
|
- name: Build and push receiptwitness image
|
||||||
uses: docker/build-push-action@v6
|
uses: docker/build-push-action@v6
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
file: ./receiptwitness/Dockerfile
|
file: ./receiptwitness/Dockerfile
|
||||||
load: true
|
push: ${{ github.event_name == 'push' }}
|
||||||
tags: ${{ steps.meta.outputs.tags }}
|
tags: ${{ steps.meta.outputs.tags }}
|
||||||
labels: ${{ steps.meta.outputs.labels }}
|
labels: ${{ steps.meta.outputs.labels }}
|
||||||
|
|
||||||
- name: Run Trivy vulnerability scanner
|
|
||||||
uses: aquasecurity/trivy-action@0.28.0
|
|
||||||
with:
|
|
||||||
image-ref: ${{ env.REGISTRY }}/${{ env.RECEIPTWITNESS_IMAGE_NAME }}:sha-${{ github.sha }}
|
|
||||||
format: 'sarif'
|
|
||||||
output: 'trivy-results-receiptwitness.sarif'
|
|
||||||
severity: 'CRITICAL,HIGH'
|
|
||||||
exit-code: '1'
|
|
||||||
|
|
||||||
- name: Upload Trivy scan results to GitHub Security
|
|
||||||
uses: github/codeql-action/upload-sarif@v3
|
|
||||||
if: always()
|
|
||||||
with:
|
|
||||||
sarif_file: 'trivy-results-receiptwitness.sarif'
|
|
||||||
|
|
||||||
- name: Push Docker image
|
|
||||||
if: github.event_name == 'push'
|
|
||||||
uses: docker/build-push-action@v6
|
|
||||||
with:
|
|
||||||
context: .
|
|
||||||
file: ./receiptwitness/Dockerfile
|
|
||||||
push: true
|
|
||||||
tags: ${{ steps.meta.outputs.tags }}
|
|
||||||
labels: ${{ steps.meta.outputs.labels }}
|
|
||||||
cache-from: type=gha
|
|
||||||
|
|
||||||
build-and-push-api:
|
build-and-push-api:
|
||||||
runs-on: runners-cartsnitch
|
runs-on: runners-cartsnitch
|
||||||
if: github.event_name == 'push'
|
if: github.event_name == 'push'
|
||||||
@@ -413,41 +335,15 @@ jobs:
|
|||||||
type=raw,value=${{ steps.calver.outputs.version }},enable=${{ github.ref == 'refs/heads/main' }}
|
type=raw,value=${{ steps.calver.outputs.version }},enable=${{ github.ref == 'refs/heads/main' }}
|
||||||
type=raw,value=latest,enable=${{ github.ref == 'refs/heads/main' }}
|
type=raw,value=latest,enable=${{ github.ref == 'refs/heads/main' }}
|
||||||
|
|
||||||
- name: Build Docker image
|
- name: Build and push API Docker image
|
||||||
uses: docker/build-push-action@v6
|
uses: docker/build-push-action@v6
|
||||||
with:
|
with:
|
||||||
context: ./api
|
context: ./api
|
||||||
file: ./api/Dockerfile
|
file: ./api/Dockerfile
|
||||||
load: true
|
push: ${{ github.event_name == 'push' }}
|
||||||
tags: ${{ steps.meta.outputs.tags }}
|
tags: ${{ steps.meta.outputs.tags }}
|
||||||
labels: ${{ steps.meta.outputs.labels }}
|
labels: ${{ steps.meta.outputs.labels }}
|
||||||
|
|
||||||
- name: Run Trivy vulnerability scanner
|
|
||||||
uses: aquasecurity/trivy-action@0.28.0
|
|
||||||
with:
|
|
||||||
image-ref: ${{ env.REGISTRY }}/${{ env.API_IMAGE_NAME }}:sha-${{ github.sha }}
|
|
||||||
format: 'sarif'
|
|
||||||
output: 'trivy-results-api.sarif'
|
|
||||||
severity: 'CRITICAL,HIGH'
|
|
||||||
exit-code: '1'
|
|
||||||
|
|
||||||
- name: Upload Trivy scan results to GitHub Security
|
|
||||||
uses: github/codeql-action/upload-sarif@v3
|
|
||||||
if: always()
|
|
||||||
with:
|
|
||||||
sarif_file: 'trivy-results-api.sarif'
|
|
||||||
|
|
||||||
- name: Push Docker image
|
|
||||||
if: github.event_name == 'push'
|
|
||||||
uses: docker/build-push-action@v6
|
|
||||||
with:
|
|
||||||
context: ./api
|
|
||||||
file: ./api/Dockerfile
|
|
||||||
push: true
|
|
||||||
tags: ${{ steps.meta.outputs.tags }}
|
|
||||||
labels: ${{ steps.meta.outputs.labels }}
|
|
||||||
cache-from: type=gha
|
|
||||||
|
|
||||||
deploy-dev:
|
deploy-dev:
|
||||||
runs-on: runners-cartsnitch
|
runs-on: runners-cartsnitch
|
||||||
needs: [build-and-push, build-and-push-auth, build-and-push-receiptwitness, build-and-push-api]
|
needs: [build-and-push, build-and-push-auth, build-and-push-receiptwitness, build-and-push-api]
|
||||||
|
|||||||
+12
-6
@@ -4,17 +4,23 @@ import pg from "pg";
|
|||||||
|
|
||||||
const { Pool } = pg;
|
const { Pool } = pg;
|
||||||
|
|
||||||
const pool = new Pool({
|
|
||||||
connectionString:
|
|
||||||
process.env.DATABASE_URL ??
|
|
||||||
"postgresql://cartsnitch:cartsnitch@localhost:5432/cartsnitch",
|
|
||||||
});
|
|
||||||
|
|
||||||
const secret = process.env.BETTER_AUTH_SECRET;
|
const secret = process.env.BETTER_AUTH_SECRET;
|
||||||
if (!secret) {
|
if (!secret) {
|
||||||
throw new Error("BETTER_AUTH_SECRET environment variable is required");
|
throw new Error("BETTER_AUTH_SECRET environment variable is required");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const databaseUrl = process.env.DATABASE_URL;
|
||||||
|
if (!databaseUrl) {
|
||||||
|
console.warn(
|
||||||
|
"WARNING: DATABASE_URL is not set — using default localhost connection. " +
|
||||||
|
"Set DATABASE_URL for production deployments."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const pool = new Pool({
|
||||||
|
connectionString: databaseUrl ?? "postgresql://cartsnitch:cartsnitch@localhost:5432/cartsnitch",
|
||||||
|
});
|
||||||
|
|
||||||
export const auth = betterAuth({
|
export const auth = betterAuth({
|
||||||
database: pool,
|
database: pool,
|
||||||
basePath: "/auth",
|
basePath: "/auth",
|
||||||
|
|||||||
+88
-1
@@ -1,4 +1,4 @@
|
|||||||
import { test as base, expect } from "@playwright/test";
|
import { test as base, expect, type Page } from "@playwright/test";
|
||||||
import AxeBuilder from "@axe-core/playwright";
|
import AxeBuilder from "@axe-core/playwright";
|
||||||
|
|
||||||
export const test = base.extend<{ axeCheck: void }>({
|
export const test = base.extend<{ axeCheck: void }>({
|
||||||
@@ -10,3 +10,90 @@ export const test = base.extend<{ axeCheck: void }>({
|
|||||||
});
|
});
|
||||||
|
|
||||||
export { expect } from "@playwright/test";
|
export { expect } from "@playwright/test";
|
||||||
|
|
||||||
|
const MOCK_USER_ID = "mock_user_123";
|
||||||
|
const MOCK_SESSION_ID = "mock_session_456";
|
||||||
|
|
||||||
|
function mockAuthRoutes(page: Page, authenticated = false) {
|
||||||
|
page.route(/.*\/auth\/sign-up\/email.*/, async (route) => {
|
||||||
|
await route.fulfill({
|
||||||
|
status: 200,
|
||||||
|
contentType: "application/json",
|
||||||
|
body: JSON.stringify({
|
||||||
|
token: null,
|
||||||
|
user: {
|
||||||
|
id: MOCK_USER_ID,
|
||||||
|
email: "mock@cartsnitch.test",
|
||||||
|
name: "Mock User",
|
||||||
|
emailVerified: true,
|
||||||
|
createdAt: new Date().toISOString(),
|
||||||
|
updatedAt: new Date().toISOString(),
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
page.route(/.*\/auth\/sign-in\/email.*/, async (route) => {
|
||||||
|
await route.fulfill({
|
||||||
|
status: 200,
|
||||||
|
contentType: "application/json",
|
||||||
|
body: JSON.stringify({
|
||||||
|
redirect: false,
|
||||||
|
token: "mock_token_123",
|
||||||
|
user: {
|
||||||
|
id: MOCK_USER_ID,
|
||||||
|
email: "mock@cartsnitch.test",
|
||||||
|
name: "Mock User",
|
||||||
|
emailVerified: true,
|
||||||
|
createdAt: new Date().toISOString(),
|
||||||
|
updatedAt: new Date().toISOString(),
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
page.route(/.*\/auth\/get-session.*/, async (route) => {
|
||||||
|
if (authenticated) {
|
||||||
|
await route.fulfill({
|
||||||
|
status: 200,
|
||||||
|
contentType: "application/json",
|
||||||
|
body: JSON.stringify({
|
||||||
|
session: {
|
||||||
|
id: MOCK_SESSION_ID,
|
||||||
|
expiresAt: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000).toISOString(),
|
||||||
|
createdAt: new Date().toISOString(),
|
||||||
|
updatedAt: new Date().toISOString(),
|
||||||
|
ipAddress: null,
|
||||||
|
userAgent: null,
|
||||||
|
},
|
||||||
|
user: {
|
||||||
|
id: MOCK_USER_ID,
|
||||||
|
email: "mock@cartsnitch.test",
|
||||||
|
name: "Mock User",
|
||||||
|
emailVerified: true,
|
||||||
|
createdAt: new Date().toISOString(),
|
||||||
|
updatedAt: new Date().toISOString(),
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
await route.fulfill({
|
||||||
|
status: 401,
|
||||||
|
contentType: "application/json",
|
||||||
|
body: JSON.stringify({ error: "Unauthorized" }),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function mockSessionPending(page: Page) {
|
||||||
|
page.route(/.*\/auth\/session.*/, async (route) => {
|
||||||
|
await route.fulfill({
|
||||||
|
status: 401,
|
||||||
|
contentType: "application/json",
|
||||||
|
body: JSON.stringify({ error: "Unauthorized" }),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export { mockAuthRoutes };
|
||||||
|
|||||||
@@ -1,18 +1,18 @@
|
|||||||
import { test, expect } from '@playwright/test';
|
import { test, expect } from '@playwright/test';
|
||||||
|
import { mockAuthRoutes } from '../fixtures';
|
||||||
|
|
||||||
const uniqueEmail = () => `betty+e2e-${Date.now()}@cartsnitch.test`;
|
const uniqueEmail = () => `betty+e2e-${Date.now()}@cartsnitch.test`;
|
||||||
|
|
||||||
test.describe('J1: Registration and Login', () => {
|
test.describe('J1: Registration and Login', () => {
|
||||||
test('can register a new account and lands on dashboard', async ({ page }) => {
|
test('can register a new account and see check your email screen', async ({ page }) => {
|
||||||
|
mockAuthRoutes(page, true);
|
||||||
await page.goto('/register');
|
await page.goto('/register');
|
||||||
await page.fill('[placeholder="Full Name"]', 'Betty Tester');
|
await page.fill('[placeholder="Full Name"]', 'Betty Tester');
|
||||||
await page.fill('[placeholder="Email"]', uniqueEmail());
|
await page.fill('[placeholder="Email"]', uniqueEmail());
|
||||||
await page.fill('[placeholder="Password (min. 8 characters)"]', 'TestPass123!');
|
await page.fill('[placeholder="Password (min. 8 characters)"]', 'TestPass123!');
|
||||||
await page.click('button[type="submit"]');
|
await page.click('button[type="submit"]');
|
||||||
|
|
||||||
// With VITE_MOCK_AUTH=true the app navigates to "/" on success
|
await expect(page.getByRole('heading', { name: /check your email/i })).toBeVisible();
|
||||||
await expect(page).toHaveURL('http://localhost:5173/');
|
|
||||||
await expect(page.getByRole('heading', { name: /cart/i })).toBeVisible();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test('shows validation error when registration fields are empty', async ({ page }) => {
|
test('shows validation error when registration fields are empty', async ({ page }) => {
|
||||||
@@ -31,22 +31,9 @@ test.describe('J1: Registration and Login', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('can sign in with credentials and land on dashboard', async ({ page }) => {
|
test('can sign in with credentials and land on dashboard', async ({ page }) => {
|
||||||
// Register first so we have a real account
|
mockAuthRoutes(page, true);
|
||||||
const email = uniqueEmail();
|
|
||||||
await page.goto('/register');
|
|
||||||
await page.fill('[placeholder="Full Name"]', 'Login Betty');
|
|
||||||
await page.fill('[placeholder="Email"]', email);
|
|
||||||
await page.fill('[placeholder="Password (min. 8 characters)"]', 'TestPass123!');
|
|
||||||
await page.click('button[type="submit"]');
|
|
||||||
await expect(page).toHaveURL('http://localhost:5173/');
|
|
||||||
|
|
||||||
// Sign out by clearing the mock session (reload with no session)
|
|
||||||
await page.goto('/');
|
|
||||||
await page.reload();
|
|
||||||
|
|
||||||
// Now sign in
|
|
||||||
await page.goto('/login');
|
await page.goto('/login');
|
||||||
await page.fill('[placeholder="Email"]', email);
|
await page.fill('[placeholder="Email"]', 'test@cartsnitch.test');
|
||||||
await page.fill('[placeholder="Password"]', 'TestPass123!');
|
await page.fill('[placeholder="Password"]', 'TestPass123!');
|
||||||
await page.click('button[type="submit"]');
|
await page.click('button[type="submit"]');
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
import { test, expect } from '@playwright/test';
|
import { test, expect } from '@playwright/test';
|
||||||
|
import { mockAuthRoutes } from '../fixtures';
|
||||||
|
|
||||||
test.describe('J8: Unauthenticated Access', () => {
|
test.describe('J8: Unauthenticated Access', () => {
|
||||||
test('redirects /dashboard (/) to /login when not authenticated', async ({ page }) => {
|
test('redirects /dashboard (/) to /login when not authenticated', async ({ page }) => {
|
||||||
// No session cookie — start fresh
|
mockAuthRoutes(page, false);
|
||||||
await page.context().clearCookies();
|
await page.context().clearCookies();
|
||||||
await page.goto('/');
|
await page.goto('/');
|
||||||
|
|
||||||
@@ -11,6 +12,7 @@ test.describe('J8: Unauthenticated Access', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('redirects /purchases to /login when not authenticated', async ({ page }) => {
|
test('redirects /purchases to /login when not authenticated', async ({ page }) => {
|
||||||
|
mockAuthRoutes(page, false);
|
||||||
await page.context().clearCookies();
|
await page.context().clearCookies();
|
||||||
await page.goto('/purchases');
|
await page.goto('/purchases');
|
||||||
|
|
||||||
@@ -19,6 +21,7 @@ test.describe('J8: Unauthenticated Access', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('redirects /products to /login when not authenticated', async ({ page }) => {
|
test('redirects /products to /login when not authenticated', async ({ page }) => {
|
||||||
|
mockAuthRoutes(page, false);
|
||||||
await page.context().clearCookies();
|
await page.context().clearCookies();
|
||||||
await page.goto('/products');
|
await page.goto('/products');
|
||||||
|
|
||||||
@@ -27,6 +30,7 @@ test.describe('J8: Unauthenticated Access', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('redirects /coupons to /login when not authenticated', async ({ page }) => {
|
test('redirects /coupons to /login when not authenticated', async ({ page }) => {
|
||||||
|
mockAuthRoutes(page, false);
|
||||||
await page.context().clearCookies();
|
await page.context().clearCookies();
|
||||||
await page.goto('/coupons');
|
await page.goto('/coupons');
|
||||||
|
|
||||||
@@ -35,15 +39,9 @@ test.describe('J8: Unauthenticated Access', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('shows loading spinner while auth session is pending', async ({ page }) => {
|
test('shows loading spinner while auth session is pending', async ({ page }) => {
|
||||||
// Intercept but don't respond — session stays pending
|
mockAuthRoutes(page, false);
|
||||||
await page.context().clearCookies();
|
await page.context().clearCookies();
|
||||||
await page.request.fetch('/api/auth/session', {
|
|
||||||
method: 'GET',
|
|
||||||
});
|
|
||||||
|
|
||||||
// Just navigate to a protected route — ProtectedRoute will show spinner while session is pending
|
|
||||||
await page.goto('/purchases');
|
await page.goto('/purchases');
|
||||||
// Spinner is visible briefly; once resolved, should redirect to login
|
|
||||||
await expect(page).toHaveURL(/\/login/, { timeout: 10_000 });
|
await expect(page).toHaveURL(/\/login/, { timeout: 10_000 });
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
+2
-1
@@ -1,6 +1,7 @@
|
|||||||
import { test, expect } from './fixtures';
|
import { test, expect, mockAuthRoutes } from './fixtures';
|
||||||
|
|
||||||
test('app loads', async ({ page }) => {
|
test('app loads', async ({ page }) => {
|
||||||
|
mockAuthRoutes(page, false);
|
||||||
await page.goto('/');
|
await page.goto('/');
|
||||||
// Unauthenticated users are redirected to /login
|
// Unauthenticated users are redirected to /login
|
||||||
await expect(page).toHaveURL(/\/login/);
|
await expect(page).toHaveURL(/\/login/);
|
||||||
|
|||||||
Generated
+3
-3
@@ -9805,9 +9805,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/vite": {
|
"node_modules/vite": {
|
||||||
"version": "6.4.1",
|
"version": "6.4.2",
|
||||||
"resolved": "https://registry.npmjs.org/vite/-/vite-6.4.1.tgz",
|
"resolved": "https://registry.npmjs.org/vite/-/vite-6.4.2.tgz",
|
||||||
"integrity": "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==",
|
"integrity": "sha512-2N/55r4JDJ4gdrCvGgINMy+HH3iRpNIz8K6SFwVsA+JbQScLiC+clmAxBgwiSPgcG9U15QmvqCGWzMbqda5zGQ==",
|
||||||
"devOptional": true,
|
"devOptional": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|||||||
@@ -9,9 +9,12 @@ export default defineConfig({
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
webServer: {
|
webServer: {
|
||||||
command: 'VITE_MOCK_AUTH=true npm run dev',
|
command: 'npm run dev',
|
||||||
url: 'http://localhost:5173',
|
url: 'http://localhost:5173',
|
||||||
reuseExistingServer: !process.env.CI,
|
reuseExistingServer: !process.env.CI,
|
||||||
|
env: {
|
||||||
|
VITE_MOCK_AUTH: 'true',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
use: {
|
use: {
|
||||||
baseURL: 'http://localhost:5173',
|
baseURL: 'http://localhost:5173',
|
||||||
|
|||||||
Reference in New Issue
Block a user