forked from cartsnitch/cartsnitch
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 129cd90551 |
+14
-106
@@ -48,59 +48,9 @@ jobs:
|
|||||||
- name: Run tests
|
- name: Run tests
|
||||||
run: npx vitest run
|
run: npx vitest run
|
||||||
|
|
||||||
audit:
|
|
||||||
runs-on: runners-cartsnitch
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
- uses: actions/setup-node@v4
|
|
||||||
with:
|
|
||||||
node-version: "20"
|
|
||||||
cache: npm
|
|
||||||
- run: npm ci
|
|
||||||
- name: Check for vulnerabilities
|
|
||||||
run: npm audit --audit-level=high
|
|
||||||
|
|
||||||
e2e:
|
|
||||||
runs-on: runners-cartsnitch
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
- uses: actions/setup-node@v4
|
|
||||||
with:
|
|
||||||
node-version: "20"
|
|
||||||
cache: npm
|
|
||||||
- run: npm ci
|
|
||||||
- run: npx playwright install --with-deps chromium
|
|
||||||
- run: npx playwright test
|
|
||||||
|
|
||||||
lighthouse:
|
|
||||||
runs-on: runners-cartsnitch
|
|
||||||
needs: [test]
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
- uses: actions/setup-node@v4
|
|
||||||
with:
|
|
||||||
node-version: "20"
|
|
||||||
cache: npm
|
|
||||||
- run: npm ci
|
|
||||||
- run: npm run build
|
|
||||||
- name: Install Chromium for Lighthouse
|
|
||||||
run: |
|
|
||||||
npm install -g playwright
|
|
||||||
npx playwright install --with-deps chromium
|
|
||||||
- name: Start preview server
|
|
||||||
run: |
|
|
||||||
npm run preview &
|
|
||||||
npx wait-on http://localhost:4173/ --timeout 30000
|
|
||||||
- name: Run Lighthouse CI
|
|
||||||
run: |
|
|
||||||
CHROME_PATH=$(find /home/runner/.cache/ms-playwright -name chrome -type f 2>/dev/null | head -1)
|
|
||||||
npm install -g @lhci/cli
|
|
||||||
LHCI_CHROME_PATH="$CHROME_PATH" lhci autorun
|
|
||||||
|
|
||||||
build-and-push:
|
build-and-push:
|
||||||
runs-on: runners-cartsnitch
|
runs-on: runners-cartsnitch
|
||||||
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
|
needs: [lint, test]
|
||||||
needs: [lint, test, e2e]
|
|
||||||
outputs:
|
outputs:
|
||||||
calver_tag: ${{ steps.calver.outputs.version }}
|
calver_tag: ${{ steps.calver.outputs.version }}
|
||||||
steps:
|
steps:
|
||||||
@@ -125,13 +75,6 @@ jobs:
|
|||||||
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
|
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
|
||||||
echo "CalVer tag: $VERSION"
|
echo "CalVer tag: $VERSION"
|
||||||
|
|
||||||
- name: Log in to Docker Hub
|
|
||||||
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
|
|
||||||
uses: docker/login-action@v3
|
|
||||||
with:
|
|
||||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
|
||||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
|
||||||
|
|
||||||
- name: Log in to GHCR
|
- name: Log in to GHCR
|
||||||
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
|
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
|
||||||
uses: docker/login-action@v3
|
uses: docker/login-action@v3
|
||||||
@@ -169,8 +112,7 @@ jobs:
|
|||||||
|
|
||||||
build-and-push-auth:
|
build-and-push-auth:
|
||||||
runs-on: runners-cartsnitch
|
runs-on: runners-cartsnitch
|
||||||
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
|
needs: [lint, test]
|
||||||
needs: [lint, test, e2e]
|
|
||||||
outputs:
|
outputs:
|
||||||
calver_tag: ${{ steps.calver.outputs.version }}
|
calver_tag: ${{ steps.calver.outputs.version }}
|
||||||
steps:
|
steps:
|
||||||
@@ -194,13 +136,6 @@ jobs:
|
|||||||
fi
|
fi
|
||||||
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
|
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
|
||||||
|
|
||||||
- name: Log in to Docker Hub
|
|
||||||
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
|
|
||||||
uses: docker/login-action@v3
|
|
||||||
with:
|
|
||||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
|
||||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
|
||||||
|
|
||||||
- name: Log in to GHCR
|
- name: Log in to GHCR
|
||||||
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
|
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
|
||||||
uses: docker/login-action@v3
|
uses: docker/login-action@v3
|
||||||
@@ -230,7 +165,6 @@ jobs:
|
|||||||
|
|
||||||
build-and-push-receiptwitness:
|
build-and-push-receiptwitness:
|
||||||
runs-on: runners-cartsnitch
|
runs-on: runners-cartsnitch
|
||||||
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
|
|
||||||
needs: [lint, test]
|
needs: [lint, test]
|
||||||
outputs:
|
outputs:
|
||||||
calver_tag: ${{ steps.calver.outputs.version }}
|
calver_tag: ${{ steps.calver.outputs.version }}
|
||||||
@@ -250,13 +184,6 @@ jobs:
|
|||||||
else BUILD_NUM=$(echo "$EXISTING" | sed "s/v${DATE_TAG}\.//"); VERSION="${DATE_TAG}.$((BUILD_NUM + 1))"; fi
|
else BUILD_NUM=$(echo "$EXISTING" | sed "s/v${DATE_TAG}\.//"); VERSION="${DATE_TAG}.$((BUILD_NUM + 1))"; fi
|
||||||
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
|
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
|
||||||
|
|
||||||
- name: Log in to Docker Hub
|
|
||||||
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
|
|
||||||
uses: docker/login-action@v3
|
|
||||||
with:
|
|
||||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
|
||||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
|
||||||
|
|
||||||
- name: Log in to GHCR
|
- name: Log in to GHCR
|
||||||
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
|
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
|
||||||
uses: docker/login-action@v3
|
uses: docker/login-action@v3
|
||||||
@@ -286,7 +213,6 @@ jobs:
|
|||||||
|
|
||||||
build-and-push-api:
|
build-and-push-api:
|
||||||
runs-on: runners-cartsnitch
|
runs-on: runners-cartsnitch
|
||||||
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
|
|
||||||
needs: [lint, test]
|
needs: [lint, test]
|
||||||
outputs:
|
outputs:
|
||||||
calver_tag: ${{ steps.calver.outputs.version }}
|
calver_tag: ${{ steps.calver.outputs.version }}
|
||||||
@@ -301,18 +227,16 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
DATE_TAG=$(date -u +%Y.%m.%d)
|
DATE_TAG=$(date -u +%Y.%m.%d)
|
||||||
EXISTING=$(git tag -l "v${DATE_TAG}*" | sort -V | tail -1)
|
EXISTING=$(git tag -l "v${DATE_TAG}*" | sort -V | tail -1)
|
||||||
if [ -z "$EXISTING" ]; then VERSION="$DATE_TAG"
|
if [ -z "$EXISTING" ]; then
|
||||||
elif [ "$EXISTING" = "v${DATE_TAG}" ]; then VERSION="${DATE_TAG}.2"
|
VERSION="$DATE_TAG"
|
||||||
else BUILD_NUM=$(echo "$EXISTING" | sed "s/v${DATE_TAG}\.//"); VERSION="${DATE_TAG}.$((BUILD_NUM + 1))"; fi
|
elif [ "$EXISTING" = "v${DATE_TAG}" ]; then
|
||||||
|
VERSION="${DATE_TAG}.2"
|
||||||
|
else
|
||||||
|
BUILD_NUM=$(echo "$EXISTING" | sed "s/v${DATE_TAG}\.//")
|
||||||
|
VERSION="${DATE_TAG}.$((BUILD_NUM + 1))"
|
||||||
|
fi
|
||||||
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
|
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
|
||||||
|
|
||||||
- name: Log in to Docker Hub
|
|
||||||
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
|
|
||||||
uses: docker/login-action@v3
|
|
||||||
with:
|
|
||||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
|
||||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
|
||||||
|
|
||||||
- name: Log in to GHCR
|
- name: Log in to GHCR
|
||||||
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
|
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
|
||||||
uses: docker/login-action@v3
|
uses: docker/login-action@v3
|
||||||
@@ -321,7 +245,7 @@ jobs:
|
|||||||
username: ${{ github.actor }}
|
username: ${{ github.actor }}
|
||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
- name: Extract metadata (API)
|
- name: Extract metadata (api)
|
||||||
id: meta
|
id: meta
|
||||||
uses: docker/metadata-action@v5
|
uses: docker/metadata-action@v5
|
||||||
with:
|
with:
|
||||||
@@ -331,7 +255,7 @@ 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 and push API Docker image
|
- name: Build and push api Docker image
|
||||||
uses: docker/build-push-action@v6
|
uses: docker/build-push-action@v6
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
@@ -343,7 +267,7 @@ jobs:
|
|||||||
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]
|
||||||
if: always() && !cancelled() && github.event_name == 'push' && github.ref == 'refs/heads/main'
|
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
|
||||||
steps:
|
steps:
|
||||||
- name: Generate GitHub App token
|
- name: Generate GitHub App token
|
||||||
id: app-token
|
id: app-token
|
||||||
@@ -368,28 +292,12 @@ jobs:
|
|||||||
- name: Install kustomize
|
- name: Install kustomize
|
||||||
uses: imranismail/setup-kustomize@v2
|
uses: imranismail/setup-kustomize@v2
|
||||||
|
|
||||||
- name: Update frontend image tag
|
- name: Update dev overlay image tags
|
||||||
if: needs.build-and-push.result == 'success'
|
|
||||||
run: |
|
run: |
|
||||||
cd infra/apps/overlays/dev
|
cd infra/apps/overlays/dev
|
||||||
kustomize edit set image ghcr.io/cartsnitch/cartsnitch:${{ needs.build-and-push.outputs.calver_tag }}
|
kustomize edit set image ghcr.io/cartsnitch/cartsnitch:${{ needs.build-and-push.outputs.calver_tag }}
|
||||||
|
|
||||||
- name: Update auth image tag
|
|
||||||
if: needs.build-and-push-auth.result == 'success'
|
|
||||||
run: |
|
|
||||||
cd infra/apps/overlays/dev
|
|
||||||
kustomize edit set image ghcr.io/cartsnitch/auth:${{ needs.build-and-push-auth.outputs.calver_tag }}
|
kustomize edit set image ghcr.io/cartsnitch/auth:${{ needs.build-and-push-auth.outputs.calver_tag }}
|
||||||
|
|
||||||
- name: Update receiptwitness image tag
|
|
||||||
if: needs.build-and-push-receiptwitness.result == 'success'
|
|
||||||
run: |
|
|
||||||
cd infra/apps/overlays/dev
|
|
||||||
kustomize edit set image ghcr.io/cartsnitch/receiptwitness:${{ needs.build-and-push-receiptwitness.outputs.calver_tag }}
|
kustomize edit set image ghcr.io/cartsnitch/receiptwitness:${{ needs.build-and-push-receiptwitness.outputs.calver_tag }}
|
||||||
|
|
||||||
- name: Update api image tag
|
|
||||||
if: needs.build-and-push-api.result == 'success'
|
|
||||||
run: |
|
|
||||||
cd infra/apps/overlays/dev
|
|
||||||
kustomize edit set image ghcr.io/cartsnitch/api:${{ needs.build-and-push-api.outputs.calver_tag }}
|
kustomize edit set image ghcr.io/cartsnitch/api:${{ needs.build-and-push-api.outputs.calver_tag }}
|
||||||
|
|
||||||
- name: Commit and push to infra
|
- name: Commit and push to infra
|
||||||
|
|||||||
+5
-10
@@ -1,5 +1,3 @@
|
|||||||
# Stage 1: Build dependencies
|
|
||||||
# Build context is the repo root. Paths below are relative to the root.
|
|
||||||
FROM python:3.12-slim AS build
|
FROM python:3.12-slim AS build
|
||||||
|
|
||||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||||
@@ -8,21 +6,18 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
|
|||||||
&& rm -rf /var/lib/apt/lists/*
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
COPY api/pyproject.toml ./
|
COPY pyproject.toml ./
|
||||||
COPY api/src/ ./src/
|
COPY src/ ./src/
|
||||||
RUN pip install --no-cache-dir --prefix=/install .
|
RUN pip install --no-cache-dir --prefix=/install .
|
||||||
|
|
||||||
# Stage 2: Production image
|
|
||||||
FROM python:3.12-slim AS prod
|
FROM python:3.12-slim AS prod
|
||||||
|
|
||||||
RUN apt-get update && apt-get install -y --no-install-recommends libpq5 && rm -rf /var/lib/apt/lists/*
|
|
||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
RUN adduser --system --group --uid 1000 app
|
RUN adduser --system --group --uid 1000 app
|
||||||
COPY --from=build /install /usr/local
|
COPY --from=build /install /usr/local
|
||||||
COPY api/src/ ./src/
|
COPY src/ ./src/
|
||||||
COPY api/alembic.ini ./
|
COPY alembic.ini ./
|
||||||
COPY api/alembic/ ./alembic/
|
COPY alembic/ ./alembic/
|
||||||
|
|
||||||
USER 1000
|
USER 1000
|
||||||
EXPOSE 8000
|
EXPOSE 8000
|
||||||
|
|||||||
@@ -36,15 +36,6 @@ export const auth = betterAuth({
|
|||||||
},
|
},
|
||||||
|
|
||||||
session: {
|
session: {
|
||||||
modelName: "sessions",
|
|
||||||
fields: {
|
|
||||||
userId: "user_id",
|
|
||||||
expiresAt: "expires_at",
|
|
||||||
ipAddress: "ip_address",
|
|
||||||
userAgent: "user_agent",
|
|
||||||
createdAt: "created_at",
|
|
||||||
updatedAt: "updated_at",
|
|
||||||
},
|
|
||||||
expiresIn: 60 * 60 * 24 * 7, // 7 days
|
expiresIn: 60 * 60 * 24 * 7, // 7 days
|
||||||
updateAge: 60 * 60 * 24, // refresh after 1 day
|
updateAge: 60 * 60 * 24, // refresh after 1 day
|
||||||
cookieCache: {
|
cookieCache: {
|
||||||
|
|||||||
@@ -1,12 +0,0 @@
|
|||||||
import { test as base, expect } from "@playwright/test";
|
|
||||||
import AxeBuilder from "@axe-core/playwright";
|
|
||||||
|
|
||||||
export const test = base.extend<{ axeCheck: void }>({
|
|
||||||
axeCheck: [async ({ page }, use) => {
|
|
||||||
await use();
|
|
||||||
const results = await new AxeBuilder({ page }).analyze();
|
|
||||||
expect(results.violations).toEqual([]);
|
|
||||||
}, { auto: true }],
|
|
||||||
});
|
|
||||||
|
|
||||||
export { expect } from "@playwright/test";
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
import { test, expect } from "./fixtures";
|
|
||||||
|
|
||||||
test("app loads", async ({ page }) => {
|
|
||||||
await page.goto("/");
|
|
||||||
await expect(page).toHaveTitle(/CartSnitch/);
|
|
||||||
});
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
{
|
|
||||||
"ci": {
|
|
||||||
"collect": {
|
|
||||||
"staticDistDir": "./dist",
|
|
||||||
"url": ["http://localhost:4173/"],
|
|
||||||
"numberOfRuns": 1
|
|
||||||
},
|
|
||||||
"assert": {
|
|
||||||
"assertions": {
|
|
||||||
"categories:performance": ["warn", { "minScore": 0.7 }],
|
|
||||||
"categories:accessibility": ["error", { "minScore": 0.9 }],
|
|
||||||
"categories:best-practices": ["warn", { "minScore": 0.8 }]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"upload": {
|
|
||||||
"target": "temporary-public-storage"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Generated
+97
-675
File diff suppressed because it is too large
Load Diff
+3
-13
@@ -9,13 +9,11 @@
|
|||||||
"lint": "eslint .",
|
"lint": "eslint .",
|
||||||
"preview": "vite preview",
|
"preview": "vite preview",
|
||||||
"test": "NODE_ENV=test vitest run",
|
"test": "NODE_ENV=test vitest run",
|
||||||
"test:watch": "NODE_ENV=test vitest",
|
"test:watch": "NODE_ENV=test vitest"
|
||||||
"test:e2e": "npx playwright test"
|
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@tanstack/react-query": "^5.0.0",
|
"@tanstack/react-query": "^5.0.0",
|
||||||
"better-auth": "^1.2.0",
|
"better-auth": "^1.2.0",
|
||||||
"picomatch": "4.0.4",
|
|
||||||
"react": "^18.3.1",
|
"react": "^18.3.1",
|
||||||
"react-dom": "^18.3.1",
|
"react-dom": "^18.3.1",
|
||||||
"react-router-dom": "^7.0.0",
|
"react-router-dom": "^7.0.0",
|
||||||
@@ -23,32 +21,24 @@
|
|||||||
"zustand": "^5.0.0"
|
"zustand": "^5.0.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@axe-core/playwright": "^4.10.0",
|
|
||||||
"@eslint/js": "^9.39.4",
|
"@eslint/js": "^9.39.4",
|
||||||
"@playwright/test": "^1.49.0",
|
|
||||||
"@tailwindcss/vite": "^4.0.0",
|
"@tailwindcss/vite": "^4.0.0",
|
||||||
"@testing-library/jest-dom": "^6.6.3",
|
"@testing-library/jest-dom": "^6.6.3",
|
||||||
"@testing-library/react": "^16.3.2",
|
"@testing-library/react": "^16.3.2",
|
||||||
"@types/node": "^24.12.0",
|
"@types/node": "^24.12.0",
|
||||||
"@types/react": "^18.3.28",
|
"@types/react": "^18.3.28",
|
||||||
"@types/react-dom": "^18.3.7",
|
"@types/react-dom": "^18.3.7",
|
||||||
"@vitejs/plugin-react": "^4.7.0",
|
"@vitejs/plugin-react": "^4.5.2",
|
||||||
"eslint": "^9.39.4",
|
"eslint": "^9.39.4",
|
||||||
"eslint-plugin-react-hooks": "^7.0.1",
|
"eslint-plugin-react-hooks": "^7.0.1",
|
||||||
"eslint-plugin-react-refresh": "^0.5.2",
|
"eslint-plugin-react-refresh": "^0.5.2",
|
||||||
"globals": "^17.4.0",
|
"globals": "^17.4.0",
|
||||||
"jsdom": "^25.0.1",
|
"jsdom": "^25.0.1",
|
||||||
"msw": "^2.12.14",
|
|
||||||
"tailwindcss": "^4.0.0",
|
"tailwindcss": "^4.0.0",
|
||||||
"typescript": "^5.7.3",
|
"typescript": "^5.7.3",
|
||||||
"typescript-eslint": "^8.56.1",
|
"typescript-eslint": "^8.56.1",
|
||||||
"vite": "^6.3.5",
|
"vite": "^6.3.5",
|
||||||
"vite-plugin-pwa": "^0.21.2",
|
"vite-plugin-pwa": "^0.21.2",
|
||||||
"vitest": "^3.2.4"
|
"vitest": "^3.2.4"
|
||||||
},
|
|
||||||
"overrides": {
|
|
||||||
"@rollup/pluginutils": "5.3.0",
|
|
||||||
"flatted": "^3.4.2",
|
|
||||||
"serialize-javascript": "7.0.5"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,19 +0,0 @@
|
|||||||
import { defineConfig, devices } from '@playwright/test';
|
|
||||||
|
|
||||||
export default defineConfig({
|
|
||||||
testDir: './e2e',
|
|
||||||
projects: [
|
|
||||||
{
|
|
||||||
name: 'chromium',
|
|
||||||
use: { ...devices['Desktop Chrome'] },
|
|
||||||
},
|
|
||||||
],
|
|
||||||
webServer: {
|
|
||||||
command: 'npm run dev',
|
|
||||||
url: 'http://localhost:5173',
|
|
||||||
reuseExistingServer: !process.env.CI,
|
|
||||||
},
|
|
||||||
use: {
|
|
||||||
baseURL: 'http://localhost:5173',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
User-agent: *
|
|
||||||
Allow: /
|
|
||||||
|
|
||||||
Sitemap: https://cartsnitch.com/sitemap.xml
|
|
||||||
@@ -1,45 +0,0 @@
|
|||||||
import { renderHook, waitFor } from '@testing-library/react'
|
|
||||||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
|
|
||||||
import { usePurchases } from '../useApi'
|
|
||||||
import { http, HttpResponse } from 'msw'
|
|
||||||
import { server } from '../../test/mocks/server'
|
|
||||||
|
|
||||||
function createWrapper() {
|
|
||||||
const queryClient = new QueryClient({
|
|
||||||
defaultOptions: { queries: { retry: false } },
|
|
||||||
})
|
|
||||||
return function Wrapper({ children }: { children: React.ReactNode }) {
|
|
||||||
return (
|
|
||||||
<QueryClientProvider client={queryClient}>
|
|
||||||
{children}
|
|
||||||
</QueryClientProvider>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
describe('useApi hooks', () => {
|
|
||||||
describe('usePurchases', () => {
|
|
||||||
it('fetches and returns purchases', async () => {
|
|
||||||
const { result } = renderHook(() => usePurchases(), { wrapper: createWrapper() })
|
|
||||||
|
|
||||||
await waitFor(() => expect(result.current.isSuccess).toBe(true))
|
|
||||||
|
|
||||||
expect(result.current.data).toHaveLength(1)
|
|
||||||
expect(result.current.data![0]).toMatchObject({
|
|
||||||
id: 'pur_1',
|
|
||||||
storeName: 'Kroger',
|
|
||||||
total: 42.5,
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
it('returns an error when the endpoint fails', async () => {
|
|
||||||
server.use(
|
|
||||||
http.get('/api/v1/purchases', () => HttpResponse.error()),
|
|
||||||
)
|
|
||||||
|
|
||||||
const { result } = renderHook(() => usePurchases(), { wrapper: createWrapper() })
|
|
||||||
|
|
||||||
await waitFor(() => expect(result.current.isError).toBe(true))
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
@@ -173,7 +173,6 @@ function AuthenticatedDashboard({ userName }: { userName: string }) {
|
|||||||
function DashboardSkeleton() {
|
function DashboardSkeleton() {
|
||||||
return (
|
return (
|
||||||
<div className="animate-pulse">
|
<div className="animate-pulse">
|
||||||
<h1 className="sr-only">Loading CartSnitch…</h1>
|
|
||||||
<div className="h-8 w-40 rounded bg-gray-200" />
|
<div className="h-8 w-40 rounded bg-gray-200" />
|
||||||
<div className="mt-4 grid grid-cols-2 gap-3">
|
<div className="mt-4 grid grid-cols-2 gap-3">
|
||||||
<div className="h-24 rounded-xl bg-gray-200" />
|
<div className="h-24 rounded-xl bg-gray-200" />
|
||||||
|
|||||||
@@ -1,65 +0,0 @@
|
|||||||
import { http, HttpResponse } from 'msw'
|
|
||||||
import type { Purchase, Product, Coupon, PriceAlert } from '../../types/api.ts'
|
|
||||||
|
|
||||||
const mockPurchases: Purchase[] = [
|
|
||||||
{
|
|
||||||
id: 'pur_1',
|
|
||||||
storeId: 'store_1',
|
|
||||||
storeName: 'Kroger',
|
|
||||||
date: '2024-01-15',
|
|
||||||
total: 42.5,
|
|
||||||
items: [
|
|
||||||
{ id: 'item_1', productId: 'prod_1', name: 'Milk', quantity: 1, price: 3.99, unitPrice: 3.99 },
|
|
||||||
{ id: 'item_2', productId: 'prod_2', name: 'Bread', quantity: 2, price: 5.98, unitPrice: 2.99 },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
const mockProducts: Product[] = [
|
|
||||||
{
|
|
||||||
id: 'prod_1',
|
|
||||||
name: 'Whole Milk',
|
|
||||||
brand: 'Kroger',
|
|
||||||
category: 'Dairy',
|
|
||||||
prices: [{ storeId: 'store_1', storeName: 'Kroger', price: 3.99, lastUpdated: '2024-01-15' }],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'prod_2',
|
|
||||||
name: 'Whole Wheat Bread',
|
|
||||||
brand: 'Nature\'s Own',
|
|
||||||
category: 'Bakery',
|
|
||||||
prices: [{ storeId: 'store_1', storeName: 'Kroger', price: 2.99, lastUpdated: '2024-01-15' }],
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
const mockCoupons: Coupon[] = [
|
|
||||||
{
|
|
||||||
id: 'coupon_1',
|
|
||||||
productId: 'prod_1',
|
|
||||||
storeName: 'Kroger',
|
|
||||||
description: '$1 off milk',
|
|
||||||
discount: '$1.00',
|
|
||||||
expiresAt: '2024-12-31',
|
|
||||||
code: 'MILK1',
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
const mockAlerts: PriceAlert[] = [
|
|
||||||
{
|
|
||||||
id: 'alert_1',
|
|
||||||
productId: 'prod_1',
|
|
||||||
productName: 'Whole Milk',
|
|
||||||
targetPrice: 2.99,
|
|
||||||
currentPrice: 3.99,
|
|
||||||
triggered: false,
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
export const handlers = [
|
|
||||||
http.get('/api/v1/health', () => HttpResponse.json({ status: 'ok' })),
|
|
||||||
http.get('/api/v1/purchases', () => HttpResponse.json(mockPurchases)),
|
|
||||||
http.get('/api/v1/products', () => HttpResponse.json(mockProducts)),
|
|
||||||
http.get('/api/v1/products/prod_1', () => HttpResponse.json(mockProducts[0])),
|
|
||||||
http.get('/api/v1/coupons', () => HttpResponse.json(mockCoupons)),
|
|
||||||
http.get('/api/v1/price-alerts', () => HttpResponse.json(mockAlerts)),
|
|
||||||
]
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
import { setupServer } from 'msw/node'
|
|
||||||
import { handlers } from './handlers'
|
|
||||||
|
|
||||||
export const server = setupServer(...handlers)
|
|
||||||
@@ -1,6 +1 @@
|
|||||||
import '@testing-library/jest-dom/vitest'
|
import '@testing-library/jest-dom/vitest'
|
||||||
import { server } from './mocks/server'
|
|
||||||
|
|
||||||
beforeAll(() => server.listen())
|
|
||||||
afterEach(() => server.resetHandlers())
|
|
||||||
afterAll(() => server.close())
|
|
||||||
|
|||||||
@@ -7,6 +7,5 @@ export default defineConfig({
|
|||||||
environment: 'jsdom',
|
environment: 'jsdom',
|
||||||
globals: true,
|
globals: true,
|
||||||
setupFiles: ['./src/test/setup.ts'],
|
setupFiles: ['./src/test/setup.ts'],
|
||||||
exclude: ['e2e/**', 'node_modules/**'],
|
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user