mirror of
https://github.com/TronoSfera/Law.git
synced 2026-05-18 10:03:45 +03:00
Test-2 commit
This commit is contained in:
parent
331973e283
commit
7b6fd8c7c2
8 changed files with 51 additions and 16 deletions
|
|
@ -3,7 +3,7 @@ from __future__ import annotations
|
|||
import re
|
||||
import uuid
|
||||
from functools import lru_cache
|
||||
from urllib.parse import quote
|
||||
from urllib.parse import quote, urlsplit
|
||||
|
||||
import boto3
|
||||
from botocore.exceptions import ClientError
|
||||
|
|
@ -46,17 +46,32 @@ class S3Storage:
|
|||
kwargs: dict = {"Bucket": self.bucket}
|
||||
if settings.S3_REGION and settings.S3_REGION != "us-east-1":
|
||||
kwargs["CreateBucketConfiguration"] = {"LocationConstraint": settings.S3_REGION}
|
||||
self.client.create_bucket(**kwargs)
|
||||
try:
|
||||
self.client.create_bucket(**kwargs)
|
||||
except ClientError as create_exc:
|
||||
create_code = str(create_exc.response.get("Error", {}).get("Code", ""))
|
||||
if create_code not in {"BucketAlreadyOwnedByYou", "BucketAlreadyExists"}:
|
||||
raise
|
||||
self._bucket_checked = True
|
||||
|
||||
@staticmethod
|
||||
def _proxy_presigned_url(raw_url: str) -> str:
|
||||
# Route pre-signed requests through frontend `/s3/*` proxy to avoid browser cross-origin issues.
|
||||
parts = urlsplit(str(raw_url or ""))
|
||||
if not parts.path:
|
||||
return raw_url
|
||||
query = ("?" + parts.query) if parts.query else ""
|
||||
return "/s3" + parts.path + query
|
||||
|
||||
def create_presigned_put_url(self, key: str, mime_type: str, expires_sec: int = 900) -> str:
|
||||
self.ensure_bucket()
|
||||
return self.client.generate_presigned_url(
|
||||
url = self.client.generate_presigned_url(
|
||||
"put_object",
|
||||
Params={"Bucket": self.bucket, "Key": key, "ContentType": mime_type},
|
||||
ExpiresIn=expires_sec,
|
||||
HttpMethod="PUT",
|
||||
)
|
||||
return self._proxy_presigned_url(url)
|
||||
|
||||
def head_object(self, key: str) -> dict:
|
||||
self.ensure_bucket()
|
||||
|
|
|
|||
|
|
@ -8,9 +8,8 @@
|
|||
</head>
|
||||
<body>
|
||||
<div id="admin-root"></div>
|
||||
<script crossorigin="anonymous" integrity="sha384-DGyLxAyjq0f9SPpVevD6IgztCFlnMF6oW/XQGmfe+IsZ8TqEiDrcHkMLKI6fiB/Z" src="https://unpkg.com/react@18/umd/react.production.min.js"></script>
|
||||
<script crossorigin="anonymous" integrity="sha384-gTGxhz21lVGYNMcdJOyq01Edg0jhn/c22nsx0kyqP0TxaV5WVdsSH1fSDUf5YJj1" src="https://unpkg.com/react-dom@18/umd/react-dom.production.min.js"></script>
|
||||
<script crossorigin="anonymous" integrity="sha384-Fo0OdKhdnE7y2WmzjOMW4PYjHkkANeu1501pWTqKrzAPeJMFQb4ZTdAA9dtrVUJV" src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
|
||||
<script type="text/babel" data-presets="env,react" src="/admin.jsx"></script>
|
||||
<script src="https://unpkg.com/react@18.2.0/umd/react.production.min.js"></script>
|
||||
<script src="https://unpkg.com/react-dom@18.2.0/umd/react-dom.production.min.js"></script>
|
||||
<script src="/admin.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
|||
|
|
@ -1661,7 +1661,6 @@
|
|||
...prev,
|
||||
statuses: Object.entries(STATUS_LABELS).map(([code, name]) => ({ code, name })),
|
||||
}));
|
||||
setTableCatalog([]);
|
||||
|
||||
if (roleOverride !== "ADMIN") return;
|
||||
|
||||
|
|
@ -1831,7 +1830,10 @@
|
|||
(tableKey, form, mode) => {
|
||||
const fields = getRecordFields(tableKey);
|
||||
const payload = {};
|
||||
const isLawyerRequestEdit = tableKey === "requests" && role === "LAWYER";
|
||||
const lawyerRequestRestricted = new Set(["assigned_lawyer_id", "effective_rate", "invoice_amount", "paid_at", "paid_by_admin_id"]);
|
||||
fields.forEach((field) => {
|
||||
if (isLawyerRequestEdit && lawyerRequestRestricted.has(field.key)) return;
|
||||
const raw = form[field.key];
|
||||
if (field.type === "boolean") {
|
||||
payload[field.key] = raw === "true";
|
||||
|
|
@ -1880,7 +1882,7 @@
|
|||
if (tableKey === "invoices" && mode === "edit") delete payload.request_track_number;
|
||||
return payload;
|
||||
},
|
||||
[getRecordFields]
|
||||
[getRecordFields, role]
|
||||
);
|
||||
|
||||
const submitRecordModal = useCallback(
|
||||
|
|
@ -2378,12 +2380,12 @@
|
|||
let cancelled = false;
|
||||
(async () => {
|
||||
await bootstrapReferenceData(token, role);
|
||||
if (!cancelled) await refreshSection(activeSection, token);
|
||||
if (!cancelled) await loadDashboard(token);
|
||||
})();
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, [bootstrapReferenceData, refreshSection, role, token]);
|
||||
}, [bootstrapReferenceData, loadDashboard, role, token]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!dictionaryTableItems.length) {
|
||||
|
|
|
|||
Binary file not shown.
|
|
@ -22,9 +22,9 @@ docker compose exec -T backend python -m compileall app tests alembic
|
|||
docker compose build frontend
|
||||
docker compose run --rm --no-deps --entrypoint sh frontend -lc "apk add --no-cache nodejs npm >/dev/null && npx --yes esbuild /usr/share/nginx/html/admin.jsx --loader:.jsx=jsx --bundle --outfile=/tmp/admin.bundle.js"
|
||||
```
|
||||
5. Браузерный E2E (Playwright) для публичного флоу:
|
||||
5. Браузерный E2E (Playwright) для ролевых UI-флоу (PUBLIC / LAWYER / ADMIN):
|
||||
```bash
|
||||
docker run --rm --network law_default -v "$PWD:/work" -w /work/e2e mcr.microsoft.com/playwright:v1.51.0-noble sh -lc "npm install && E2E_BASE_URL=http://frontend npx playwright test --config=playwright.config.js"
|
||||
docker run --rm --network law_default -v "$PWD:/work" -w /work/e2e mcr.microsoft.com/playwright:v1.58.2-jammy sh -lc "npm install --silent && E2E_BASE_URL=http://frontend E2E_ADMIN_EMAIL=admin@example.com E2E_ADMIN_PASSWORD='AdminPass-123!' E2E_LAWYER_EMAIL=ivan@mail.ru E2E_LAWYER_PASSWORD='LawyerPass-123!' npx playwright test --config=playwright.config.js"
|
||||
```
|
||||
|
||||
## Матрица проверок по задачам
|
||||
|
|
@ -60,7 +60,7 @@ docker run --rm --network law_default -v "$PWD:/work" -w /work/e2e mcr.microsoft
|
|||
|
||||
## Ролевое покрытие (PUBLIC / LAWYER / ADMIN)
|
||||
### PUBLIC (клиент)
|
||||
- Лендинг и клиентский контур (ручной e2e через `http://localhost:8081`): открыть лендинг, создать заявку, открыть кабинет.
|
||||
- Лендинг и клиентский контур через UI e2e: `e2e/tests/public_client_flow.spec.js` (создание заявки, кабинет, чат, загрузка файла).
|
||||
- OTP create/view + 7-day cookie + rate-limit: `tests/test_public_requests.py`, `tests/test_otp_rate_limit.py`.
|
||||
- Просмотр статуса/истории/чата/файлов/таймлайна по `track_number`: `tests/test_public_cabinet.py`.
|
||||
- Переписка клиент -> юрист и маркеры непрочитанного: `tests/test_public_cabinet.py`, `tests/test_notifications.py`.
|
||||
|
|
@ -68,6 +68,7 @@ docker run --rm --network law_default -v "$PWD:/work" -w /work/e2e mcr.microsoft
|
|||
- Публичные счета и PDF в кабинете: `tests/test_invoices.py`.
|
||||
|
||||
### LAWYER (юрист)
|
||||
- UI e2e: `e2e/tests/lawyer_role_flow.spec.js` (вход, claim неназначенной заявки, чтение обновлений, смена статуса).
|
||||
- Дашборд юриста (свои, неназначенные, непрочитанные): `tests/test_dashboard_finance.py`.
|
||||
- Видимость заявок: свои + неназначенные; запрет доступа к чужим: `tests/test_admin_universal_crud.py`.
|
||||
- Claim неназначенной заявки, запрет takeover, запрет назначения через CRUD: `tests/test_admin_universal_crud.py`.
|
||||
|
|
@ -77,6 +78,7 @@ docker run --rm --network law_default -v "$PWD:/work" -w /work/e2e mcr.microsoft
|
|||
- Счета: видимость только своих, запрет ставить `PAID`: `tests/test_invoices.py`, `tests/test_billing_flow.py`.
|
||||
|
||||
### ADMIN (администратор)
|
||||
- UI e2e: `e2e/tests/admin_role_flow.spec.js` (вход, справочники, создание пользователя/темы, создание и оплата счета).
|
||||
- CRUD пользователей/юристов (пароли, роли, профильная тема, аватар): `tests/test_admin_universal_crud.py`, `tests/test_uploads_s3.py`.
|
||||
- Темы и флоу статусов (включая ветвление), SLA-переходы: `tests/test_admin_universal_crud.py`, `tests/test_worker_maintenance.py`.
|
||||
- Шаблоны обязательных/дозапрашиваемых данных: `tests/test_admin_universal_crud.py`, `tests/test_public_requests.py`.
|
||||
|
|
@ -94,4 +96,4 @@ docker run --rm --network law_default -v "$PWD:/work" -w /work/e2e mcr.microsoft
|
|||
|
||||
## Последний регрессионный прогон
|
||||
- `python -m unittest discover -s tests -p 'test_*.py' -v` — `94 tests OK`.
|
||||
- `Playwright public flow` (`e2e/tests/public_client_flow.spec.js`) — `1 passed`.
|
||||
- `Playwright UI roles` (`e2e/tests/admin_role_flow.spec.js`, `e2e/tests/lawyer_role_flow.spec.js`, `e2e/tests/public_client_flow.spec.js`) — `3 passed`.
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
"test": "playwright test --config=playwright.config.js"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@playwright/test": "^1.51.0",
|
||||
"@playwright/test": "1.58.2",
|
||||
"dotenv": "^16.4.5",
|
||||
"jsonwebtoken": "^9.0.2"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,12 @@
|
|||
FROM node:22-alpine AS admin-build
|
||||
WORKDIR /build
|
||||
COPY app/web/admin.jsx ./admin.jsx
|
||||
RUN npm init -y >/dev/null 2>&1 \
|
||||
&& npm install --silent esbuild@0.25.10 \
|
||||
&& npx esbuild admin.jsx --loader:.jsx=jsx --format=iife --target=es2018 --outfile=admin.js
|
||||
|
||||
FROM nginx:1.27-alpine
|
||||
COPY frontend/nginx.conf /etc/nginx/conf.d/default.conf
|
||||
COPY app/web/ /usr/share/nginx/html/
|
||||
COPY --from=admin-build /build/admin.js /usr/share/nginx/html/admin.js
|
||||
RUN cp /usr/share/nginx/html/landing.html /usr/share/nginx/html/index.html
|
||||
|
|
|
|||
|
|
@ -40,6 +40,15 @@ server {
|
|||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
}
|
||||
|
||||
location /s3/ {
|
||||
proxy_pass http://minio:9000/;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host minio:9000;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
}
|
||||
|
||||
location /health {
|
||||
proxy_pass http://backend:8000/health;
|
||||
proxy_http_version 1.1;
|
||||
|
|
|
|||
Loading…
Reference in a new issue