Refactored frontend directory
Some checks failed
Chore App Build, Test, and Push Docker Images / build-and-push (push) Failing after 2m46s

This commit is contained in:
2026-04-25 00:40:15 -04:00
parent db846f4e31
commit 127378797c
263 changed files with 88 additions and 79 deletions

View File

@@ -44,15 +44,15 @@ jobs:
with:
node-version: "20.19.0"
cache: "npm"
cache-dependency-path: frontend/vue-app/package-lock.json
cache-dependency-path: frontend/package-lock.json
- name: Install frontend dependencies
run: npm ci
working-directory: frontend/vue-app
working-directory: frontend
- name: Run frontend unit tests
run: npm run test:unit --if-present
working-directory: frontend/vue-app
working-directory: frontend
- name: Build Backend Docker Image
run: |
@@ -60,7 +60,7 @@ jobs:
- name: Build Frontend Docker Image
run: |
docker build -t git.ryankegel.com:3000/kegel/chores/frontend:${{ steps.vars.outputs.tag }} ./frontend/vue-app
docker build -t git.ryankegel.com:3000/kegel/chores/frontend:${{ steps.vars.outputs.tag }} ./frontend
- name: Log in to Registry
uses: docker/login-action@v2

View File

@@ -17,7 +17,7 @@ You are a Senior UI/UX Architect specializing in clean, highly reactive web appl
This is the **Reward** app — a family chore/reward tracker. Vue 3 (Composition API / `<script setup lang="ts">`) frontend. Key files:
- Theme tokens: `frontend/vue-app/src/assets/colors.css`**always read this first** when reviewing a component.
- Theme tokens: `frontend/src/assets/colors.css`**always read this first** when reviewing a component.
- Layout wrappers: `ParentLayout` (admin views) and `ChildLayout` (child dashboard views).
- All `.vue` files use `<style scoped>`. Child component styling uses `:deep()` selectors.
- File order within `.vue` files: `<template>`, then `<script>`, then `<style>`.

View File

@@ -5,7 +5,7 @@
- **Stack**: Flask (Python, backend) + Vue 3 (TypeScript, frontend) + TinyDB (JSON, thread-safe, see `db/`).
- **API**: RESTful endpoints in `api/`, grouped by entity (child, reward, task, user, image, etc). Each API file maps to a business domain.
- **Nginx Proxy**: Frontend nginx proxies `/api/*` to backend, stripping the `/api` prefix. Backend endpoints should NOT include `/api` in their route definitions. Example: Backend defines `@app.route('/user')`, frontend calls `/api/user`.
- **Models**: Maintain strict 1:1 mapping between Python `@dataclass`es (`backend/models/`) and TypeScript interfaces (`frontend/vue-app/src/common/models.ts`).
- **Models**: Maintain strict 1:1 mapping between Python `@dataclass`es (`backend/models/`) and TypeScript interfaces (`frontend/src/common/models.ts`).
- **Database**: Use TinyDB with `from_dict()`/`to_dict()` for serialization. All logic should operate on model instances, not raw dicts.
- **Events**: Real-time updates via Server-Sent Events (SSE). Every mutation (add/edit/delete/trigger) must call `send_event_for_current_user` (see `backend/events/`).
- **Changes**: Do not use comments to replace code. All changes must be reflected in both backend and frontend files as needed.
@@ -25,7 +25,7 @@
## 🚦 Frontend Logic & Event Bus
- **SSE Event Management**: Register listeners in `onMounted`, clean up in `onUnmounted`. Listen for events like `child_task_triggered`, `child_reward_request`, `task_modified`, etc. See `frontend/vue-app/src/common/backendEvents.ts` and `components/BackendEventsListener.vue`.
- **SSE Event Management**: Register listeners in `onMounted`, clean up in `onUnmounted`. Listen for events like `child_task_triggered`, `child_reward_request`, `task_modified`, etc. See `frontend/src/common/backendEvents.ts` and `components/BackendEventsListener.vue`.
- **Layout Hierarchy**: Use `ParentLayout` for admin/management, `ChildLayout` for dashboard/focus views.
## ⚖️ Business Logic & Safeguards
@@ -37,9 +37,9 @@
- **Backend**: Run Flask with `python -m flask run --host=0.0.0.0 --port=5000` from the `backend/` directory. Main entry: `backend/main.py`.
- **Virtual Env**: Python is running from a virtual environment located at `backend/.venv/`.
- **Frontend**: From `frontend/vue-app/`, run `npm install` then `npm run dev`.
- **Tests**: Run backend tests with `pytest` in `backend/tests/`. Frontend component tests: `npm run test` in `frontend/vue-app/components/__tests__/`. E2E tests: `npx playwright test` from `frontend/vue-app/` — requires both servers running (use the `flask-backend` and `vue-frontend` skills).
- **E2E Setup**: Playwright config is at `frontend/vue-app/playwright.config.ts`. Tests live in `frontend/vue-app/tests/`. The `globalSetup` in `playwright.config.ts` seeds the database and logs in once; all tests receive a pre-authenticated session via `storageState` — do NOT navigate to `/auth/login` in tests. Import `E2E_EMAIL` and `E2E_PASSWORD` from `tests/global-setup.ts` rather than hardcoding credentials. The backend must be started with `DB_ENV=e2e DATA_ENV=e2e` (the `flask-backend` skill does this) so test data goes to `backend/test_data/` and never touches production data.
- **Frontend**: From `frontend/`, run `npm install` then `npm run dev`.
- **Tests**: Run backend tests with `pytest` in `backend/tests/`. Frontend component tests: `npm run test` in `frontend/components/__tests__/`. E2E tests: `npx playwright test` from `frontend/` — requires both servers running (use the `flask-backend` and `vue-frontend` skills).
- **E2E Setup**: Playwright config is at `frontend/playwright.config.ts`. Tests live in `frontend/tests/`. The `globalSetup` in `playwright.config.ts` seeds the database and logs in once; all tests receive a pre-authenticated session via `storageState` — do NOT navigate to `/auth/login` in tests. Import `E2E_EMAIL` and `E2E_PASSWORD` from `tests/global-setup.ts` rather than hardcoding credentials. The backend must be started with `DB_ENV=e2e DATA_ENV=e2e` (the `flask-backend` skill does this) so test data goes to `backend/test_data/` and never touches production data.
- **Debugging**: Use VS Code launch configs or run Flask/Vue dev servers directly. For SSE, use browser dev tools to inspect event streams.
## 📁 Key Files & Directories
@@ -48,10 +48,10 @@
- `backend/models/` — Python dataclasses (business logic, serialization)
- `backend/db/` — TinyDB setup and helpers
- `backend/events/` — SSE event types, broadcaster, payloads
- `frontend/vue-app/` — Vue 3 frontend (see `src/common/`, `src/components/`, `src/layout/`) - Where tests are run from
- `frontend/vue-app/src/common/models.ts` — TypeScript interfaces (mirror Python models)
- `frontend/vue-app/src/common/api.ts` — API helpers, error parsing, validation
- `frontend/vue-app/src/common/backendEvents.ts` — SSE event types and handlers
- `frontend/` — Vue 3 frontend (see `src/common/`, `src/components/`, `src/layout/`) - Where tests are run from
- `frontend/src/common/models.ts` — TypeScript interfaces (mirror Python models)
- `frontend/src/common/api.ts` — API helpers, error parsing, validation
- `frontend/src/common/backendEvents.ts` — SSE event types and handlers
## 🧠 Integration & Cross-Component Patterns

View File

@@ -8,10 +8,10 @@ disable-model-invocation: true
Use this skill when the user wants to "start the frontend," "run vue," or "launch the dev server."
1. **Verify Directory:** Navigate to `./frontend/vue-app`.
1. **Verify Directory:** Navigate to `./frontend`.
- _Self-Correction:_ If the directory doesn't exist, search the workspace for `package.json` files and ask for clarification.
2. **Check Dependencies:** - Before running, check if `node_modules` exists in `./frontend/vue-app`.
2. **Check Dependencies:** - Before running, check if `node_modules` exists in `./frontend`.
- If missing, ask the user: "Should I run `npm install` first?"
3. **Execution:** - Run the command: `npm run dev`

10
.gitignore vendored
View File

@@ -1,12 +1,12 @@
.env
backend/test_data/
logs/
frontend/vue-app/resources/
frontend/vue-app/playwright-report/
frontend/vue-app/test-results/
frontend/resources/
frontend/playwright-report/
frontend/test-results/
backend/test-results/
.vscode/keybindings.json
.DS_Store
**/.DS_Store
frontend/vue-app/cert.pem
frontend/vue-app/key.pem
frontend/cert.pem
frontend/key.pem

6
.vscode/launch.json vendored
View File

@@ -35,7 +35,7 @@
"run",
"dev"
],
"cwd": "${workspaceFolder}/frontend/vue-app",
"cwd": "${workspaceFolder}/frontend",
"console": "integratedTerminal",
"serverReadyAction": {
"pattern": "Local:.*https://localhost:([0-9]+)",
@@ -48,7 +48,7 @@
"type": "pwa-chrome",
"request": "launch",
"url": "http://localhost:5173",
"webRoot": "${workspaceFolder}/frontend/vue-app"
"webRoot": "${workspaceFolder}/frontend"
},
{
"name": "Python: Backend Tests",
@@ -76,7 +76,7 @@
"run",
"test:unit"
],
"cwd": "${workspaceFolder}/frontend/vue-app",
"cwd": "${workspaceFolder}/frontend",
"console": "integratedTerminal",
"osx": {
"env": {

View File

@@ -28,7 +28,7 @@
"run",
"dev"
],
"cwd": "${workspaceFolder}/frontend/vue-app",
"cwd": "${workspaceFolder}/frontend",
"console": "integratedTerminal"
},
{
@@ -36,7 +36,7 @@
"type": "chrome",
"request": "launch",
"url": "https://localhost:5173", // or your Vite dev server port
"webRoot": "${workspaceFolder}/frontend/vue-app"
"webRoot": "${workspaceFolder}/frontend"
},
{
"name": "Python: Backend Tests",
@@ -60,7 +60,7 @@
"runtimeArgs": [
"vitest"
],
"cwd": "${workspaceFolder}/frontend/vue-app",
"cwd": "${workspaceFolder}/frontend",
"console": "integratedTerminal"
}
],

2
.vscode/mcp.json vendored
View File

@@ -8,7 +8,7 @@
"run-test-mcp-server",
"--config=playwright.config.ts"
],
"cwd": "frontend/vue-app"
"cwd": "frontend"
}
},
"inputs": []

22
.vscode/tasks.json vendored
View File

@@ -44,56 +44,56 @@
{
"label": "PW: Task Modification Tests",
"type": "shell",
"command": "cd \"${workspaceFolder}/frontend/vue-app\" && npx playwright test e2e/mode_parent/task-modification --project=chromium-task-modification",
"command": "cd \"${workspaceFolder}/frontend\" && npx playwright test e2e/mode_parent/task-modification --project=chromium-task-modification",
"isBackground": false,
"group": "test"
},
{
"label": "PW: Task Modification Tests (PS)",
"type": "shell",
"command": "powershell -Command \"cd '$env:APPDATA/../../../d/Python Utilities/Reward/frontend/vue-app'; npx playwright test e2e/mode_parent/task-modification --project=chromium-task-modification\"",
"command": "powershell -Command \"cd '$env:APPDATA/../../../d/Python Utilities/Reward/frontend'; npx playwright test e2e/mode_parent/task-modification --project=chromium-task-modification\"",
"isBackground": false,
"group": "test"
},
{
"label": "PW: Task Modification Tests (cmd)",
"type": "shell",
"command": "cmd /c \"cd /d \\\"D:\\Python Utilities\\Reward\\frontend\\vue-app\\\" && npx playwright test e2e/mode_parent/task-modification --project=chromium-task-modification 2>&1\"",
"command": "cmd /c \"cd /d \\\"D:\\Python Utilities\\Reward\\frontend\\\" && npx playwright test e2e/mode_parent/task-modification --project=chromium-task-modification 2>&1\"",
"isBackground": false,
"group": "test"
},
{
"label": "PW: User Profile Tests",
"type": "shell",
"command": "cd \"${workspaceFolder}/frontend/vue-app\" && npx playwright test e2e/mode_parent/user-profile --project=chromium-user-profile --project=chromium-user-profile-pin --project=chromium-user-profile-delete",
"command": "cd \"${workspaceFolder}/frontend\" && npx playwright test e2e/mode_parent/user-profile --project=chromium-user-profile --project=chromium-user-profile-pin --project=chromium-user-profile-delete",
"isBackground": false,
"group": "test"
},
{
"label": "PW: User Profile Tests 2",
"type": "shell",
"command": "cd \"${workspaceFolder}/frontend/vue-app\" && npx playwright test e2e/mode_parent/user-profile --project=chromium-user-profile --project=chromium-user-profile-pin --project=chromium-user-profile-delete",
"command": "cd \"${workspaceFolder}/frontend\" && npx playwright test e2e/mode_parent/user-profile --project=chromium-user-profile --project=chromium-user-profile-pin --project=chromium-user-profile-delete",
"isBackground": false,
"group": "test"
},
{
"label": "PW: User Profile Tests Final",
"type": "shell",
"command": "cd \"${workspaceFolder}/frontend/vue-app\" && npx playwright test e2e/mode_parent/user-profile --project=chromium-user-profile --project=chromium-user-profile-pin --project=chromium-user-profile-delete",
"command": "cd \"${workspaceFolder}/frontend\" && npx playwright test e2e/mode_parent/user-profile --project=chromium-user-profile --project=chromium-user-profile-pin --project=chromium-user-profile-delete",
"isBackground": false,
"group": "test"
},
{
"label": "PW: Full Test Suite",
"type": "shell",
"command": "cd \"${workspaceFolder}/frontend/vue-app\" && npx playwright test --reporter=line",
"command": "cd \"${workspaceFolder}/frontend\" && npx playwright test --reporter=line",
"isBackground": false,
"group": "test"
},
{
"label": "PW: Full Suite",
"type": "shell",
"command": "cd \"${workspaceFolder}/frontend/vue-app\" && npx playwright test --reporter=line",
"command": "cd \"${workspaceFolder}/frontend\" && npx playwright test --reporter=line",
"isBackground": false,
"group": "test"
},
@@ -104,7 +104,7 @@
"args": [
"-NoProfile",
"-Command",
"cd 'D:\\Python Utilities\\Reward\\frontend\\vue-app'; npx playwright test --reporter=line 2>&1"
"cd 'D:\\Python Utilities\\Reward\\frontend'; npx playwright test --reporter=line 2>&1"
],
"group": "test",
"presentation": {
@@ -119,7 +119,7 @@
"args": [
"-NoProfile",
"-Command",
"cd 'D:\\Python Utilities\\Reward\\frontend\\vue-app'; npx playwright test e2e/mode_parent/tasks/penalty-default.spec.ts --project=chromium-default-tasks --reporter=line 2>&1"
"cd 'D:\\Python Utilities\\Reward\\frontend'; npx playwright test e2e/mode_parent/tasks/penalty-default.spec.ts --project=chromium-default-tasks --reporter=line 2>&1"
],
"group": "test",
"presentation": {
@@ -134,7 +134,7 @@
"args": [
"-NoProfile",
"-Command",
"cd 'D:\\Python Utilities\\Reward\\frontend\\vue-app'; npx playwright test e2e/mode_parent/user-profile/profile-editing.spec.ts --project=chromium-user-profile --reporter=line 2>&1"
"cd 'D:\\Python Utilities\\Reward\\frontend'; npx playwright test e2e/mode_parent/user-profile/profile-editing.spec.ts --project=chromium-user-profile --reporter=line 2>&1"
],
"group": "test",
"presentation": {

View File

@@ -24,7 +24,7 @@ python -m flask run --host=0.0.0.0 --port=5000
### Frontend
```bash
cd frontend/vue-app
cd frontend
npm install
npm run dev
```
@@ -114,7 +114,7 @@ pytest tests/
### Frontend Tests
```bash
cd frontend/vue-app
cd frontend
npm run test
```
@@ -151,11 +151,10 @@ npm run test
│ ├── tests/ # Backend tests
│ └── utils/ # Utilities (scheduler, etc)
├── frontend/
│ └── vue-app/
── src/
├── common/ # Shared utilities
├── components/ # Vue components
│ └── layout/ # Layout components
│ └── src/
── common/ # Shared utilities
│ ├── components/ # Vue components
└── layout/ # Layout components
└── .github/
└── specs/ # Feature specifications
```

View File

@@ -1,4 +1,4 @@
# vue-app
# frontend
This template should help get you started developing with Vue 3 in Vite.
@@ -9,7 +9,7 @@ This template should help get you started developing with Vue 3 in Vite.
## Recommended Browser Setup
- Chromium-based browsers (Chrome, Edge, Brave, etc.):
- [Vue.js devtools](https://chromewebstore.google.com/detail/vuejs-devtools/nhdogjmejiglipccpnnnanhbledajbpd)
- [Vue.js devtools](https://chromewebstore.google.com/detail/vuejs-devtools/nhdogjmejiglipccpnnnanhbledajbpd)
- [Turn on Custom Object Formatter in Chrome DevTools](http://bit.ly/object-formatters)
- Firefox:
- [Vue.js devtools](https://addons.mozilla.org/en-US/firefox/addon/vue-js-devtools/)

View File

Before

Width:  |  Height:  |  Size: 156 KiB

After

Width:  |  Height:  |  Size: 156 KiB

View File

Before

Width:  |  Height:  |  Size: 156 KiB

After

Width:  |  Height:  |  Size: 156 KiB

View File

@@ -2,20 +2,20 @@
"cookies": [
{
"name": "refresh_token",
"value": "yQZhoeeYUidDfolHE467zNEWguWXzMIinFT1LNLU5yM",
"value": "oh4sQB_Q1Se4xydZHTIolU6FhvlKPIlDPsxfd_qe4wQ",
"domain": "localhost",
"path": "/api/auth",
"expires": 1784509278.633774,
"expires": 1784867824.852037,
"httpOnly": true,
"secure": true,
"sameSite": "Strict"
},
{
"name": "access_token",
"value": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6ImUyZS1jY0B0ZXN0LmNvbSIsInVzZXJfaWQiOiI4ZmZkYTMyNC01MDE1LTRiYjktODY3Yi1jZjFlNTU1NjBjMzciLCJ0b2tlbl92ZXJzaW9uIjowLCJleHAiOjE3NzY3MzQxNzh9.YvFGLV1bdoRpRb8z5YtO19dpD7vkC0p5XMhGJgSJol8",
"value": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6ImUyZS1jY0B0ZXN0LmNvbSIsInVzZXJfaWQiOiI4NGY1Y2I3NS05NWU1LTRjMTItOTExOS02MzdkYjJlMWY1MGYiLCJ0b2tlbl92ZXJzaW9uIjowLCJleHAiOjE3NzcwOTI3MjR9.7-d4EvJEYtYSi_5ZFiU7tuxgc4ilVMBxhwzgAQZNTvY",
"domain": "localhost",
"path": "/",
"expires": 1776734178.633726,
"expires": 1777092724.851439,
"httpOnly": true,
"secure": true,
"sameSite": "Lax"
@@ -27,11 +27,11 @@
"localStorage": [
{
"name": "authSyncEvent",
"value": "{\"type\":\"logout\",\"at\":1776733278500}"
"value": "{\"type\":\"logout\",\"at\":1777091824594}"
},
{
"name": "parentAuth",
"value": "{\"expiresAt\":1776906078798}"
"value": "{\"expiresAt\":1777264625262}"
}
]
}

View File

@@ -2,20 +2,20 @@
"cookies": [
{
"name": "refresh_token",
"value": "j90QCKu8d9QvNG-k-m31G5PoxU0yBuz-wyx8NPYCOG8",
"value": "lIhurtvmo0oGcimN_8v59h9y3CrglEAloQO1QFw8RsQ",
"domain": "localhost",
"path": "/api/auth",
"expires": 1784509278.541961,
"expires": 1784867824.995155,
"httpOnly": true,
"secure": true,
"sameSite": "Strict"
},
{
"name": "access_token",
"value": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6ImUyZS1kZWxldGVAdGVzdC5jb20iLCJ1c2VyX2lkIjoiNzA3ZGYwYmUtOWQxMy00MGVlLWI3M2QtYzYwZmFmNDg2NmU2IiwidG9rZW5fdmVyc2lvbiI6MCwiZXhwIjoxNzc2NzM0MTc4fQ.cEKtxo4zVTd7kBZzfjjI_uxqez32gUxmZYuUa8-RYdc",
"value": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6ImUyZS1kZWxldGVAdGVzdC5jb20iLCJ1c2VyX2lkIjoiOWYzYTljNWMtM2IwMy00YTZlLTliNmUtYjE0MTQzZWNhMzNkIiwidG9rZW5fdmVyc2lvbiI6MCwiZXhwIjoxNzc3MDkyNzI0fQ.UB5MCIZ2PHbn3UyohNzmFbOCb3TR-KHxJPWB9qkWwCQ",
"domain": "localhost",
"path": "/",
"expires": 1776734178.541918,
"expires": 1777092724.99406,
"httpOnly": true,
"secure": true,
"sameSite": "Lax"
@@ -27,11 +27,11 @@
"localStorage": [
{
"name": "authSyncEvent",
"value": "{\"type\":\"logout\",\"at\":1776733278395}"
"value": "{\"type\":\"logout\",\"at\":1777091824687}"
},
{
"name": "parentAuth",
"value": "{\"expiresAt\":1776906078675}"
"value": "{\"expiresAt\":1777264625511}"
}
]
}

View File

@@ -2,20 +2,20 @@
"cookies": [
{
"name": "refresh_token",
"value": "RETiHvb_RUs2-0xb-klx8BwvSwr2TMtz1D-NmB5825Y",
"value": "uMExObHukEdFnlszorcDq2XlbRW0AeIV71x_vSovEF4",
"domain": "localhost",
"path": "/api/auth",
"expires": 1784509276.929905,
"expires": 1784867821.107956,
"httpOnly": true,
"secure": true,
"sameSite": "Strict"
},
{
"name": "access_token",
"value": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6ImUyZUB0ZXN0LmNvbSIsInVzZXJfaWQiOiJhNjZmYjdjNy04ZDI5LTRkNjAtYmVjNi0zMmEzODg1MGI1OGIiLCJ0b2tlbl92ZXJzaW9uIjowLCJleHAiOjE3NzY3MzQxNzZ9.WSka-ZbN1ZKbD_Q4UpoP9qfN1i73G51nrx88F2TbbOE",
"value": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6ImUyZUB0ZXN0LmNvbSIsInVzZXJfaWQiOiI2ZWE5YWZmZC1jMGExLTRkMzUtOGJmYS1mZDBkOWY5NzFmOWQiLCJ0b2tlbl92ZXJzaW9uIjowLCJleHAiOjE3NzcwOTI3MjF9.rb64zKqS6uXqkgX_4kke37Rc6om68elt4Qa24Fy9NPo",
"domain": "localhost",
"path": "/",
"expires": 1776734176.929863,
"expires": 1777092721.107403,
"httpOnly": true,
"secure": true,
"sameSite": "Lax"
@@ -27,11 +27,11 @@
"localStorage": [
{
"name": "authSyncEvent",
"value": "{\"type\":\"logout\",\"at\":1776733276784}"
"value": "{\"type\":\"logout\",\"at\":1777091820880}"
},
{
"name": "parentAuth",
"value": "{\"expiresAt\":1776906077058}"
"value": "{\"expiresAt\":1777264621275}"
}
]
}

View File

Before

Width:  |  Height:  |  Size: 409 KiB

After

Width:  |  Height:  |  Size: 409 KiB

View File

@@ -98,8 +98,9 @@ test.describe('Digest Action Token — Approve Chore', () => {
test('Approve-chore action is performed: points awarded and confirmation removed', async ({
request,
}) => {
// Wait for action to complete
await new Promise((r) => setTimeout(r, 300))
// Execute the action via authenticated POST (GET only redirects, POST consumes the token)
const execRes = await request.post(`/api/digest-action/${approveToken}`)
expect(execRes.status()).toBe(200)
const childRes = await request.get(`/api/child/list`)
const children = (await childRes.json()).children ?? []

View File

@@ -93,7 +93,9 @@ test.describe('Digest Action Token — Approve Reward', () => {
// 7.6 Approve-reward action: reward triggered, child points deducted
test('Approve-reward action: reward triggered and child points deducted', async ({ request }) => {
await new Promise((r) => setTimeout(r, 300))
// Execute the action via authenticated POST (GET only redirects, POST consumes the token)
const execRes = await request.post(`/api/digest-action/${approveToken}`)
expect(execRes.status()).toBe(200)
const childRes = await request.get('/api/child/list')
const children = (await childRes.json()).children ?? []

View File

@@ -87,7 +87,9 @@ test.describe('Digest Action Token — Deny Chore', () => {
test('Deny-chore action is performed: confirmation removed, points unchanged', async ({
request,
}) => {
await new Promise((r) => setTimeout(r, 300))
// Execute the action via authenticated POST (GET only redirects, POST consumes the token)
const execRes = await request.post(`/api/digest-action/${denyToken}`)
expect(execRes.status()).toBe(200)
const childRes = await request.get(`/api/child/list`)
const children = (await childRes.json()).children ?? []

View File

@@ -92,7 +92,9 @@ test.describe('Digest Action Token — Deny Reward', () => {
// 7.8 Deny-reward action: pending request removed, points unchanged
test('Deny-reward action: pending request removed and points unchanged', async ({ request }) => {
await new Promise((r) => setTimeout(r, 300))
// Execute the action via authenticated POST (GET only redirects, POST consumes the token)
const execRes = await request.post(`/api/digest-action/${denyToken}`)
expect(execRes.status()).toBe(200)
const childRes = await request.get('/api/child/list')
const children = (await childRes.json()).children ?? []

View File

@@ -94,11 +94,11 @@ test.describe('Digest Action Token — Error Paths', () => {
const unauthCtx = await getUnauthContext(playwright)
// First use — should succeed (302)
const first = await unauthCtx.get(`/api/digest-action/${token}`, { maxRedirects: 0 })
expect(first.status()).toBe(302)
// First use — authenticated POST executes and consumes the token
const first = await request.post(`/api/digest-action/${token}`)
expect(first.status()).toBe(200)
// Second use — token is consumed
// Second use — token is consumed, unauthenticated GET should return 400
const second = await unauthCtx.get(`/api/digest-action/${token}`, { maxRedirects: 0 })
expect(second.status()).toBe(400)
await unauthCtx.dispose()

Some files were not shown because too many files have changed in this diff Show More