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: with:
node-version: "20.19.0" node-version: "20.19.0"
cache: "npm" cache: "npm"
cache-dependency-path: frontend/vue-app/package-lock.json cache-dependency-path: frontend/package-lock.json
- name: Install frontend dependencies - name: Install frontend dependencies
run: npm ci run: npm ci
working-directory: frontend/vue-app working-directory: frontend
- name: Run frontend unit tests - name: Run frontend unit tests
run: npm run test:unit --if-present run: npm run test:unit --if-present
working-directory: frontend/vue-app working-directory: frontend
- name: Build Backend Docker Image - name: Build Backend Docker Image
run: | run: |
@@ -60,7 +60,7 @@ jobs:
- name: Build Frontend Docker Image - name: Build Frontend Docker Image
run: | 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 - name: Log in to Registry
uses: docker/login-action@v2 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: 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). - Layout wrappers: `ParentLayout` (admin views) and `ChildLayout` (child dashboard views).
- All `.vue` files use `<style scoped>`. Child component styling uses `:deep()` selectors. - All `.vue` files use `<style scoped>`. Child component styling uses `:deep()` selectors.
- File order within `.vue` files: `<template>`, then `<script>`, then `<style>`. - 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/`). - **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. - **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`. - **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. - **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/`). - **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. - **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 ## 🚦 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. - **Layout Hierarchy**: Use `ParentLayout` for admin/management, `ChildLayout` for dashboard/focus views.
## ⚖️ Business Logic & Safeguards ## ⚖️ 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`. - **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/`. - **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`. - **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/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). - **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/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. - **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. - **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 ## 📁 Key Files & Directories
@@ -48,10 +48,10 @@
- `backend/models/` — Python dataclasses (business logic, serialization) - `backend/models/` — Python dataclasses (business logic, serialization)
- `backend/db/` — TinyDB setup and helpers - `backend/db/` — TinyDB setup and helpers
- `backend/events/` — SSE event types, broadcaster, payloads - `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 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/src/common/models.ts` — TypeScript interfaces (mirror Python models)
- `frontend/vue-app/src/common/api.ts` — API helpers, error parsing, validation - `frontend/src/common/api.ts` — API helpers, error parsing, validation
- `frontend/vue-app/src/common/backendEvents.ts` — SSE event types and handlers - `frontend/src/common/backendEvents.ts` — SSE event types and handlers
## 🧠 Integration & Cross-Component Patterns ## 🧠 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." 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. - _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?" - If missing, ask the user: "Should I run `npm install` first?"
3. **Execution:** - Run the command: `npm run dev` 3. **Execution:** - Run the command: `npm run dev`

10
.gitignore vendored
View File

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

6
.vscode/launch.json vendored
View File

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

View File

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

2
.vscode/mcp.json vendored
View File

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

22
.vscode/tasks.json vendored
View File

@@ -44,56 +44,56 @@
{ {
"label": "PW: Task Modification Tests", "label": "PW: Task Modification Tests",
"type": "shell", "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, "isBackground": false,
"group": "test" "group": "test"
}, },
{ {
"label": "PW: Task Modification Tests (PS)", "label": "PW: Task Modification Tests (PS)",
"type": "shell", "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, "isBackground": false,
"group": "test" "group": "test"
}, },
{ {
"label": "PW: Task Modification Tests (cmd)", "label": "PW: Task Modification Tests (cmd)",
"type": "shell", "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, "isBackground": false,
"group": "test" "group": "test"
}, },
{ {
"label": "PW: User Profile Tests", "label": "PW: User Profile Tests",
"type": "shell", "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, "isBackground": false,
"group": "test" "group": "test"
}, },
{ {
"label": "PW: User Profile Tests 2", "label": "PW: User Profile Tests 2",
"type": "shell", "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, "isBackground": false,
"group": "test" "group": "test"
}, },
{ {
"label": "PW: User Profile Tests Final", "label": "PW: User Profile Tests Final",
"type": "shell", "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, "isBackground": false,
"group": "test" "group": "test"
}, },
{ {
"label": "PW: Full Test Suite", "label": "PW: Full Test Suite",
"type": "shell", "type": "shell",
"command": "cd \"${workspaceFolder}/frontend/vue-app\" && npx playwright test --reporter=line", "command": "cd \"${workspaceFolder}/frontend\" && npx playwright test --reporter=line",
"isBackground": false, "isBackground": false,
"group": "test" "group": "test"
}, },
{ {
"label": "PW: Full Suite", "label": "PW: Full Suite",
"type": "shell", "type": "shell",
"command": "cd \"${workspaceFolder}/frontend/vue-app\" && npx playwright test --reporter=line", "command": "cd \"${workspaceFolder}/frontend\" && npx playwright test --reporter=line",
"isBackground": false, "isBackground": false,
"group": "test" "group": "test"
}, },
@@ -104,7 +104,7 @@
"args": [ "args": [
"-NoProfile", "-NoProfile",
"-Command", "-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", "group": "test",
"presentation": { "presentation": {
@@ -119,7 +119,7 @@
"args": [ "args": [
"-NoProfile", "-NoProfile",
"-Command", "-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", "group": "test",
"presentation": { "presentation": {
@@ -134,7 +134,7 @@
"args": [ "args": [
"-NoProfile", "-NoProfile",
"-Command", "-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", "group": "test",
"presentation": { "presentation": {

View File

@@ -24,7 +24,7 @@ python -m flask run --host=0.0.0.0 --port=5000
### Frontend ### Frontend
```bash ```bash
cd frontend/vue-app cd frontend
npm install npm install
npm run dev npm run dev
``` ```
@@ -114,7 +114,7 @@ pytest tests/
### Frontend Tests ### Frontend Tests
```bash ```bash
cd frontend/vue-app cd frontend
npm run test npm run test
``` ```
@@ -151,11 +151,10 @@ npm run test
│ ├── tests/ # Backend tests │ ├── tests/ # Backend tests
│ └── utils/ # Utilities (scheduler, etc) │ └── utils/ # Utilities (scheduler, etc)
├── frontend/ ├── frontend/
│ └── vue-app/ │ └── src/
── src/ ── common/ # Shared utilities
├── common/ # Shared utilities │ ├── components/ # Vue components
├── components/ # Vue components └── layout/ # Layout components
│ └── layout/ # Layout components
└── .github/ └── .github/
└── specs/ # Feature specifications └── 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. This template should help get you started developing with Vue 3 in Vite.

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

View File

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

View File

@@ -2,20 +2,20 @@
"cookies": [ "cookies": [
{ {
"name": "refresh_token", "name": "refresh_token",
"value": "RETiHvb_RUs2-0xb-klx8BwvSwr2TMtz1D-NmB5825Y", "value": "uMExObHukEdFnlszorcDq2XlbRW0AeIV71x_vSovEF4",
"domain": "localhost", "domain": "localhost",
"path": "/api/auth", "path": "/api/auth",
"expires": 1784509276.929905, "expires": 1784867821.107956,
"httpOnly": true, "httpOnly": true,
"secure": true, "secure": true,
"sameSite": "Strict" "sameSite": "Strict"
}, },
{ {
"name": "access_token", "name": "access_token",
"value": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6ImUyZUB0ZXN0LmNvbSIsInVzZXJfaWQiOiJhNjZmYjdjNy04ZDI5LTRkNjAtYmVjNi0zMmEzODg1MGI1OGIiLCJ0b2tlbl92ZXJzaW9uIjowLCJleHAiOjE3NzY3MzQxNzZ9.WSka-ZbN1ZKbD_Q4UpoP9qfN1i73G51nrx88F2TbbOE", "value": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6ImUyZUB0ZXN0LmNvbSIsInVzZXJfaWQiOiI2ZWE5YWZmZC1jMGExLTRkMzUtOGJmYS1mZDBkOWY5NzFmOWQiLCJ0b2tlbl92ZXJzaW9uIjowLCJleHAiOjE3NzcwOTI3MjF9.rb64zKqS6uXqkgX_4kke37Rc6om68elt4Qa24Fy9NPo",
"domain": "localhost", "domain": "localhost",
"path": "/", "path": "/",
"expires": 1776734176.929863, "expires": 1777092721.107403,
"httpOnly": true, "httpOnly": true,
"secure": true, "secure": true,
"sameSite": "Lax" "sameSite": "Lax"
@@ -27,11 +27,11 @@
"localStorage": [ "localStorage": [
{ {
"name": "authSyncEvent", "name": "authSyncEvent",
"value": "{\"type\":\"logout\",\"at\":1776733276784}" "value": "{\"type\":\"logout\",\"at\":1777091820880}"
}, },
{ {
"name": "parentAuth", "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 ({ test('Approve-chore action is performed: points awarded and confirmation removed', async ({
request, request,
}) => { }) => {
// Wait for action to complete // Execute the action via authenticated POST (GET only redirects, POST consumes the token)
await new Promise((r) => setTimeout(r, 300)) const execRes = await request.post(`/api/digest-action/${approveToken}`)
expect(execRes.status()).toBe(200)
const childRes = await request.get(`/api/child/list`) const childRes = await request.get(`/api/child/list`)
const children = (await childRes.json()).children ?? [] 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 // 7.6 Approve-reward action: reward triggered, child points deducted
test('Approve-reward action: reward triggered and child points deducted', async ({ request }) => { 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 childRes = await request.get('/api/child/list')
const children = (await childRes.json()).children ?? [] 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 ({ test('Deny-chore action is performed: confirmation removed, points unchanged', async ({
request, 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 childRes = await request.get(`/api/child/list`)
const children = (await childRes.json()).children ?? [] 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 // 7.8 Deny-reward action: pending request removed, points unchanged
test('Deny-reward action: pending request removed and points unchanged', async ({ request }) => { 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 childRes = await request.get('/api/child/list')
const children = (await childRes.json()).children ?? [] const children = (await childRes.json()).children ?? []

View File

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

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