chore: update TypeScript to 6.0.1-rc and adjust package dependencies
- Removed duplicate @typescript-eslint/utils dependency in frontend/package.json - Updated TypeScript version from 5.9.3 to 6.0.1-rc in frontend/package.json and package.json - Adjusted ResizeObserver mock to use globalThis in tests - Modified tsconfig.json and tsconfig.node.json to include empty types array - Cleaned up package-lock.json to reflect TypeScript version change and updated dev dependencies
This commit is contained in:
@@ -21,6 +21,24 @@ Imagine you have several apps running on your computer. Maybe a blog, a file sto
|
||||
|
||||
## Step 1: Install Charon
|
||||
|
||||
### Required Secrets (Generate Before Installing)
|
||||
|
||||
Two secrets must be set before starting Charon. Omitting them will cause **sessions to reset on every container restart**, locking users out.
|
||||
|
||||
Generate both values now and keep them somewhere safe:
|
||||
|
||||
```bash
|
||||
# JWT secret — signs and validates login sessions
|
||||
openssl rand -hex 32
|
||||
|
||||
# Encryption key — protects stored credentials at rest
|
||||
openssl rand -base64 32
|
||||
```
|
||||
|
||||
> **Why this matters:** If `CHARON_JWT_SECRET` is not set, Charon generates a random key on each boot. Any active login session becomes invalid the moment the container restarts, producing a "Session validation failed" error.
|
||||
|
||||
---
|
||||
|
||||
### Option A: Docker Compose (Easiest)
|
||||
|
||||
Create a file called `docker-compose.yml`:
|
||||
@@ -43,6 +61,8 @@ services:
|
||||
- /var/run/docker.sock:/var/run/docker.sock:ro
|
||||
environment:
|
||||
- CHARON_ENV=production
|
||||
- CHARON_JWT_SECRET=<output of: openssl rand -hex 32>
|
||||
- CHARON_ENCRYPTION_KEY=<output of: openssl rand -base64 32>
|
||||
```
|
||||
|
||||
Then run:
|
||||
@@ -64,6 +84,8 @@ docker run -d \
|
||||
-v ./charon-data:/app/data \
|
||||
-v /var/run/docker.sock:/var/run/docker.sock:ro \
|
||||
-e CHARON_ENV=production \
|
||||
-e CHARON_JWT_SECRET=<output of: openssl rand -hex 32> \
|
||||
-e CHARON_ENCRYPTION_KEY=<output of: openssl rand -base64 32> \
|
||||
wikid82/charon:latest
|
||||
```
|
||||
|
||||
@@ -78,6 +100,8 @@ docker run -d \
|
||||
-v ./charon-data:/app/data \
|
||||
-v /var/run/docker.sock:/var/run/docker.sock:ro \
|
||||
-e CHARON_ENV=production \
|
||||
-e CHARON_JWT_SECRET=<output of: openssl rand -hex 32> \
|
||||
-e CHARON_ENCRYPTION_KEY=<output of: openssl rand -base64 32> \
|
||||
ghcr.io/wikid82/charon:latest
|
||||
```
|
||||
|
||||
|
||||
497
docs/plans/archive/telegram_test_remediation_spec.md
Normal file
497
docs/plans/archive/telegram_test_remediation_spec.md
Normal file
@@ -0,0 +1,497 @@
|
||||
# Telegram Notification Provider — Test Failure Remediation Plan
|
||||
|
||||
**Date:** 2026-03-11
|
||||
**Author:** Planning Agent
|
||||
**Status:** Remediation Required — All security scans pass, test failures block merge
|
||||
**Previous Plan:** Archived as `docs/plans/telegram_implementation_spec.md`
|
||||
|
||||
---
|
||||
|
||||
## 1. Introduction
|
||||
|
||||
The Telegram notification provider feature is functionally complete with passing security scans and coverage gates. However, **56 E2E test failures** and **2 frontend unit test failures** block the PR merge. This plan identifies root causes, categorises each failure set, and provides specific remediation steps.
|
||||
|
||||
### Failure Summary
|
||||
|
||||
| Spec File | Failures | Browsers | Unique Est. | Category |
|
||||
|---|---|---|---|---|
|
||||
| `notifications.spec.ts` | 48 | 3 | ~16 | **Our change** |
|
||||
| `notifications-payload.spec.ts` | 18 | 3 | ~6 | **Our change** |
|
||||
| `telegram-notification-provider.spec.ts` | 4 | 1–3 | ~2 | **Our change** |
|
||||
| `encryption-management.spec.ts` | 20 | 3 | ~7 | Pre-existing |
|
||||
| `auth-middleware-cascade.spec.ts` | 18 | 3 | 6 | Pre-existing |
|
||||
| `Notifications.test.tsx` (unit) | 2 | — | 2 | **Our change** |
|
||||
|
||||
CI retries: 2 per test (`playwright.config.js` L144). Failure counts above represent unique test failures × browser projects.
|
||||
|
||||
---
|
||||
|
||||
## 2. Root Cause Analysis
|
||||
|
||||
### Root Cause A: `isNew` Guard on Test Button (CRITICAL — Causes ~80% of failures)
|
||||
|
||||
**What changed:** The Telegram feature added a guard in `Notifications.tsx` (L117-124) that blocks the "Test" button for new (unsaved) providers:
|
||||
|
||||
```typescript
|
||||
// Line 117-124: handleTest() early return guard
|
||||
const handleTest = () => {
|
||||
const formData = watch();
|
||||
const currentType = normalizeProviderType(formData.type);
|
||||
if (!formData.id && currentType !== 'email') {
|
||||
toast.error(t('notificationProviders.saveBeforeTesting'));
|
||||
return;
|
||||
}
|
||||
testMutation.mutate({ ...formData, type: currentType } as Partial<NotificationProvider>);
|
||||
};
|
||||
```
|
||||
|
||||
And a `disabled` attribute on the test button at `Notifications.tsx` (L382):
|
||||
|
||||
```typescript
|
||||
// Line 382: Button disabled state
|
||||
disabled={testMutation.isPending || (isNew && !isEmail)}
|
||||
```
|
||||
|
||||
**Why it was added:** The backend `Test` handler at `notification_provider_handler.go` (L333-336) requires a saved provider ID for all non-email types. For Gotify/Telegram, the server needs the stored token. For Discord/Webhook, the server still fetches the provider from DB. Without a saved provider, the backend returns `MISSING_PROVIDER_ID`.
|
||||
|
||||
**Why it breaks tests:** Many existing E2E and unit tests click the test button from a **new (unsaved) provider form** using mocked endpoints. With the new guard:
|
||||
1. The `<button>` is `disabled` → browser ignores clicks → mocked routes never receive requests
|
||||
2. Even if not disabled, `handleTest()` returns early with a toast instead of calling `testMutation.mutate()`
|
||||
3. Tests that `waitForRequest` on `/providers/test` time out (60s default)
|
||||
4. Tests that assert on `capturedTestPayload` find `null`
|
||||
|
||||
**Is the guard correct?** Yes — it matches the backend's security-by-design constraint. The tests need to be adapted to the new behavior, not the guard removed.
|
||||
|
||||
### Root Cause B: Pre-existing Infrastructure Failures (encryption-management, auth-middleware-cascade)
|
||||
|
||||
**encryption-management.spec.ts** (17 tests, ~7 unique failures) navigates to `/security/encryption` and tests key rotation, validation, and history display. **Zero overlap** with notification provider code paths. No files modified in the Telegram PR affect encryption.
|
||||
|
||||
**auth-middleware-cascade.spec.ts** (6 tests, all 6 fail) uses deprecated `waitUntil: 'networkidle'`, creates proxy hosts via UI forms (`getByLabel(/domain/i)`), and tests auth flows through Caddy. **Zero overlap** with notification code. These tests have known fragility from UI element selectors and `networkidle` waits.
|
||||
|
||||
**Verdict:** Both are pre-existing failures. They should be tracked separately and not block the Telegram PR.
|
||||
|
||||
### Root Cause C: Telegram E2E Spec Issues (4 failures)
|
||||
|
||||
The `telegram-notification-provider.spec.ts` has 8 tests, with ~2 unique failures. Most likely candidates:
|
||||
|
||||
1. **"should edit telegram notification provider and preserve token"** (L159): Uses fragile keyboard navigation (focus Send Test → Tab → Enter) to reach the Edit button. If the `title` attribute on the Send Test button doesn't match the accessible name pattern `/send test/i`, or if the tab order is affected by any intermediate focusable element, the Enter press activates the wrong button or nothing at all.
|
||||
|
||||
2. **"should test telegram notification provider"** (L265): Clicks the row-level "Send Test" button. The locator uses `getByRole('button', { name: /send test/i })`. The button has `title={t('notificationProviders.sendTest')}` which renders as "Send Test". This should work, but the `title` attribute contributing to accessible name can be browser-dependent, particularly in WebKit.
|
||||
|
||||
---
|
||||
|
||||
## 3. Affected Tests — Complete Inventory
|
||||
|
||||
### 3.1 E2E Tests: `notifications.spec.ts` (Test Button on New Form)
|
||||
|
||||
These tests open the "Add Provider" form (no `id`), click `provider-test-btn`, and expect API interactions. The disabled button now prevents all interaction.
|
||||
|
||||
| # | Test Name | Line | Type Used | Failure Mode |
|
||||
|---|---|---|---|---|
|
||||
| 1 | should test notification provider | L1085 | discord | `waitForRequest` times out — button disabled |
|
||||
| 2 | should show test success feedback | L1142 | discord | Success icon never appears — no click fires |
|
||||
| 3 | should preserve Discord request payload contract for save, preview, and test | L1236 | discord | `capturedTestPayload` is null — button disabled |
|
||||
| 4 | should show error when test fails | L1665 | discord | Error icon never appears — no click fires |
|
||||
|
||||
**Additional cascade effects:** The user reports ~16 unique failures from this file. The 4 above are directly caused by the `isNew` guard. Remaining failures may stem from cascading timeout effects, `beforeEach` state leakage after long timeouts, or other pre-existing flakiness amplified by the 60s timeout waterfall.
|
||||
|
||||
### 3.2 E2E Tests: `notifications-payload.spec.ts` (Test Button on New Form)
|
||||
|
||||
| # | Test Name | Line | Type Used | Failure Mode |
|
||||
|---|---|---|---|---|
|
||||
| 1 | provider-specific transformation strips gotify token from test and preview payloads | L264 | gotify | `provider-test-btn` disabled for new gotify form; `capturedTestPayload` is null |
|
||||
| 2 | retry split distinguishes retryable and non-retryable failures | L410 | webhook | `provider-test-btn` disabled for new webhook form; `waitForResponse` times out |
|
||||
|
||||
**Tests that should still pass:**
|
||||
- `valid payload flows for discord, gotify, and webhook` (L54) — uses `provider-save-btn`, not test button
|
||||
- `malformed payload scenarios` (L158) — API-level tests via `page.request.post`
|
||||
- `missing required fields block submit` (L192) — uses save button
|
||||
- `auth/header behavior checks` (L217) — API-level tests
|
||||
- `security: SSRF` (L314) — API-level tests
|
||||
- `security: DNS-rebinding` (L381) — API-level tests
|
||||
- `security: token does not leak` (L512) — API-level tests
|
||||
|
||||
### 3.3 E2E Tests: `telegram-notification-provider.spec.ts`
|
||||
|
||||
| # | Test Name | Line | Probable Failure Mode |
|
||||
|---|---|---|---|
|
||||
| 1 | should edit telegram notification provider and preserve token | L159 | Keyboard navigation (Tab from Send Test → Edit) fragility; may hit wrong element on some browsers |
|
||||
| 2 | should test telegram notification provider | L265 | Row-level Send Test button; possible accessible name mismatch in WebKit with `title` attribute |
|
||||
|
||||
**Tests that should pass:**
|
||||
- Form rendering tests (L25, L65) — UI assertions only
|
||||
- Create telegram provider (L89) — mocked POST
|
||||
- Delete telegram provider (L324) — mocked DELETE + confirm dialog
|
||||
- Security tests (L389, L436) — mock-based assertions
|
||||
|
||||
### 3.4 Frontend Unit Tests: `Notifications.test.tsx`
|
||||
|
||||
| # | Test Name | Line | Failure Mode |
|
||||
|---|---|---|---|
|
||||
| 1 | submits provider test action from form using normalized discord type | L447 | `userEvent.click()` on disabled button is no-op → `testProvider` never called → `waitFor` times out |
|
||||
| 2 | shows error toast when test mutation fails | L569 | Same — disabled button prevents click → `toast.error` with `saveBeforeTesting` fires instead of mutation error |
|
||||
|
||||
### 3.5 Pre-existing (Not Caused By Telegram PR)
|
||||
|
||||
| Spec | Tests | Rationale |
|
||||
|---|---|---|
|
||||
| `encryption-management.spec.ts` | ~7 unique | Tests encryption page at `/security/encryption`. No code overlap. |
|
||||
| `auth-middleware-cascade.spec.ts` | 6 unique | Tests proxy creation + auth middleware. Uses `networkidle`. No code overlap. |
|
||||
|
||||
---
|
||||
|
||||
## 4. Remediation Plan
|
||||
|
||||
### Priority Order
|
||||
|
||||
1. **P0 — Fix unit tests** (fastest, unblocks local dev verification)
|
||||
2. **P1 — Fix E2E test-button tests** (the core regression from our change)
|
||||
3. **P2 — Fix telegram spec fragility** (new tests we added)
|
||||
4. **P3 — Document pre-existing failures** (not our change, track separately)
|
||||
|
||||
---
|
||||
|
||||
### 4.1 P0: Frontend Unit Test Fixes
|
||||
|
||||
**File:** `frontend/src/pages/__tests__/Notifications.test.tsx`
|
||||
|
||||
#### Fix 1: "submits provider test action from form using normalized discord type" (L447)
|
||||
|
||||
**Problem:** Test opens "Add Provider" (new form, no `id`), clicks test button. Button is now disabled for new providers.
|
||||
|
||||
**Fix:** Change to test from an **existing provider's edit form** instead of a new form. This preserves the original intent (verifying the test payload uses normalized type).
|
||||
|
||||
```typescript
|
||||
// BEFORE (L447-462):
|
||||
it('submits provider test action from form using normalized discord type', async () => {
|
||||
vi.mocked(notificationsApi.testProvider).mockResolvedValue()
|
||||
const user = userEvent.setup()
|
||||
renderWithQueryClient(<Notifications />)
|
||||
|
||||
await user.click(await screen.findByTestId('add-provider-btn'))
|
||||
await user.type(screen.getByTestId('provider-name'), 'Preview/Test Provider')
|
||||
await user.type(screen.getByTestId('provider-url'), 'https://example.com/webhook')
|
||||
await user.click(screen.getByTestId('provider-test-btn'))
|
||||
|
||||
await waitFor(() => {
|
||||
expect(notificationsApi.testProvider).toHaveBeenCalled()
|
||||
})
|
||||
const payload = vi.mocked(notificationsApi.testProvider).mock.calls[0][0]
|
||||
expect(payload.type).toBe('discord')
|
||||
})
|
||||
|
||||
// AFTER:
|
||||
it('submits provider test action from form using normalized discord type', async () => {
|
||||
vi.mocked(notificationsApi.testProvider).mockResolvedValue()
|
||||
setupMocks([baseProvider]) // baseProvider has an id
|
||||
const user = userEvent.setup()
|
||||
renderWithQueryClient(<Notifications />)
|
||||
|
||||
// Open edit form for existing provider (has id → test button enabled)
|
||||
const row = await screen.findByTestId(`provider-row-${baseProvider.id}`)
|
||||
const buttons = within(row).getAllByRole('button')
|
||||
await user.click(buttons[1]) // Edit button
|
||||
|
||||
await user.click(screen.getByTestId('provider-test-btn'))
|
||||
|
||||
await waitFor(() => {
|
||||
expect(notificationsApi.testProvider).toHaveBeenCalled()
|
||||
})
|
||||
const payload = vi.mocked(notificationsApi.testProvider).mock.calls[0][0]
|
||||
expect(payload.type).toBe('discord')
|
||||
})
|
||||
```
|
||||
|
||||
#### Fix 2: "shows error toast when test mutation fails" (L569)
|
||||
|
||||
**Problem:** Same — test opens new form, clicks test button, expects mutation error toast. Button is disabled.
|
||||
|
||||
**Fix:** Test from an existing provider's edit form.
|
||||
|
||||
```typescript
|
||||
// BEFORE (L569-582):
|
||||
it('shows error toast when test mutation fails', async () => {
|
||||
vi.mocked(notificationsApi.testProvider).mockRejectedValue(new Error('Connection refused'))
|
||||
const user = userEvent.setup()
|
||||
renderWithQueryClient(<Notifications />)
|
||||
|
||||
await user.click(await screen.findByTestId('add-provider-btn'))
|
||||
await user.type(screen.getByTestId('provider-name'), 'Failing Provider')
|
||||
await user.type(screen.getByTestId('provider-url'), 'https://example.com/webhook')
|
||||
await user.click(screen.getByTestId('provider-test-btn'))
|
||||
|
||||
await waitFor(() => {
|
||||
expect(toast.error).toHaveBeenCalledWith('Connection refused')
|
||||
})
|
||||
})
|
||||
|
||||
// AFTER:
|
||||
it('shows error toast when test mutation fails', async () => {
|
||||
vi.mocked(notificationsApi.testProvider).mockRejectedValue(new Error('Connection refused'))
|
||||
setupMocks([baseProvider])
|
||||
const user = userEvent.setup()
|
||||
renderWithQueryClient(<Notifications />)
|
||||
|
||||
// Open edit form for existing provider
|
||||
const row = await screen.findByTestId(`provider-row-${baseProvider.id}`)
|
||||
const buttons = within(row).getAllByRole('button')
|
||||
await user.click(buttons[1]) // Edit button
|
||||
|
||||
await user.click(screen.getByTestId('provider-test-btn'))
|
||||
|
||||
await waitFor(() => {
|
||||
expect(toast.error).toHaveBeenCalledWith('Connection refused')
|
||||
})
|
||||
})
|
||||
```
|
||||
|
||||
#### Bonus: Add a NEW unit test for the `saveBeforeTesting` guard
|
||||
|
||||
```typescript
|
||||
it('disables test button when provider is new (unsaved) and not email type', async () => {
|
||||
const user = userEvent.setup()
|
||||
renderWithQueryClient(<Notifications />)
|
||||
|
||||
await user.click(await screen.findByTestId('add-provider-btn'))
|
||||
const testBtn = screen.getByTestId('provider-test-btn')
|
||||
expect(testBtn).toBeDisabled()
|
||||
})
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 4.2 P1: E2E Test Fixes — notifications.spec.ts
|
||||
|
||||
**File:** `tests/settings/notifications.spec.ts`
|
||||
|
||||
**Strategy:** For tests that click the test button from a new form, restructure the flow to:
|
||||
1. First **save** the provider (mocked create → returns id)
|
||||
2. Then **test** from the saved provider row's Send Test button (row buttons are not gated by `isNew`)
|
||||
|
||||
#### Fix 3: "should test notification provider" (L1085)
|
||||
|
||||
**Current flow:** Add form → fill → mock test endpoint → click `provider-test-btn` → verify request
|
||||
**Problem:** Test button disabled for new form
|
||||
**Fix:** Save first, then click test from the provider row's Send Test button.
|
||||
|
||||
```typescript
|
||||
// In the test, after filling the form and before clicking test:
|
||||
|
||||
// 1. Mock the create endpoint to return a provider with an id
|
||||
await page.route('**/api/v1/notifications/providers', async (route, request) => {
|
||||
if (request.method() === 'POST') {
|
||||
const payload = await request.postDataJSON();
|
||||
await route.fulfill({
|
||||
status: 201,
|
||||
contentType: 'application/json',
|
||||
body: JSON.stringify({ id: 'saved-test-id', ...payload }),
|
||||
});
|
||||
} else if (request.method() === 'GET') {
|
||||
await route.fulfill({
|
||||
status: 200,
|
||||
contentType: 'application/json',
|
||||
body: JSON.stringify([{
|
||||
id: 'saved-test-id',
|
||||
name: 'Test Provider',
|
||||
type: 'discord',
|
||||
url: 'https://discord.com/api/webhooks/test/token',
|
||||
enabled: true
|
||||
}]),
|
||||
});
|
||||
} else {
|
||||
await route.continue();
|
||||
}
|
||||
});
|
||||
|
||||
// 2. Save the provider first
|
||||
await page.getByTestId('provider-save-btn').click();
|
||||
|
||||
// 3. Wait for the provider to appear in the list
|
||||
await expect(page.getByText('Test Provider')).toBeVisible({ timeout: 5000 });
|
||||
|
||||
// 4. Click row-level Send Test button
|
||||
const providerRow = page.getByTestId('provider-row-saved-test-id');
|
||||
const sendTestButton = providerRow.getByRole('button', { name: /send test/i });
|
||||
await sendTestButton.click();
|
||||
```
|
||||
|
||||
#### Fix 4: "should show test success feedback" (L1142)
|
||||
|
||||
Same pattern as Fix 3: save provider first, then test from row.
|
||||
|
||||
#### Fix 5: "should preserve Discord request payload contract for save, preview, and test" (L1236)
|
||||
|
||||
**Current flow:** Add form → fill → click preview → click test → save → verify all payloads
|
||||
**Problem:** Test button disabled for new form
|
||||
**Fix:** Reorder to: Add form → fill → click preview → **save** → **test from row** → verify payloads
|
||||
|
||||
The preview button is NOT disabled for new forms (only the test button is), so preview still works from the new form. The test step must happen after save.
|
||||
|
||||
#### Fix 6: "should show error when test fails" (L1665)
|
||||
|
||||
Same pattern: save first, then test from row.
|
||||
|
||||
---
|
||||
|
||||
### 4.3 P1: E2E Test Fixes — notifications-payload.spec.ts
|
||||
|
||||
**File:** `tests/settings/notifications-payload.spec.ts`
|
||||
|
||||
#### Fix 7: "provider-specific transformation strips gotify token from test and preview payloads" (L264)
|
||||
|
||||
**Current flow:** Add gotify form → fill with token → click preview → click test → verify token not in payloads
|
||||
**Problem:** Test button disabled for new gotify form
|
||||
**Fix:** Preview still works from new form. For test, save first, then test from the saved provider row.
|
||||
|
||||
**Note:** The row-level test call uses `{ ...provider, type: normalizeProviderType(provider.type) }` where `provider` is the list item (which never contains `token/gotify_token` per the List handler that strips tokens). So the token-stripping assertion naturally holds for row-level tests.
|
||||
|
||||
#### Fix 8: "retry split distinguishes retryable and non-retryable failures" (L410)
|
||||
|
||||
**Current flow:** Add webhook form → fill → click test → verify retry semantics
|
||||
**Problem:** Test button disabled for new webhook form
|
||||
**Fix:** Save first (mock create), then open edit form (which has `id`) or test from the row.
|
||||
|
||||
---
|
||||
|
||||
### 4.4 P2: Telegram E2E Spec Hardening
|
||||
|
||||
**File:** `tests/settings/telegram-notification-provider.spec.ts`
|
||||
|
||||
#### Fix 9: "should edit telegram notification provider and preserve token" (L159)
|
||||
|
||||
**Problem:** Uses fragile keyboard navigation to reach the Edit button:
|
||||
```typescript
|
||||
await sendTestButton.focus();
|
||||
await page.keyboard.press('Tab');
|
||||
await page.keyboard.press('Enter');
|
||||
```
|
||||
|
||||
This assumes Tab from Send Test lands on Edit. Tab order can vary across browsers.
|
||||
|
||||
**Fix:** Use a direct locator for the Edit button instead of keyboard navigation:
|
||||
|
||||
```typescript
|
||||
// BEFORE:
|
||||
await sendTestButton.focus();
|
||||
await page.keyboard.press('Tab');
|
||||
await page.keyboard.press('Enter');
|
||||
|
||||
// AFTER:
|
||||
const editButton = providerRow.getByRole('button').nth(1); // Send Test=0, Edit=1
|
||||
await editButton.click();
|
||||
```
|
||||
|
||||
Or use a structural locator based on the edit icon class.
|
||||
|
||||
#### Fix 10: "should test telegram notification provider" (L265)
|
||||
|
||||
**Probable issue:** The `getByRole('button', { name: /send test/i })` relies on `title` for accessible name. WebKit may not compute accessible name from `title` the same way.
|
||||
|
||||
**Fix (source — preferred):** Add explicit `aria-label` to the row Send Test button in `Notifications.tsx` (L703):
|
||||
```tsx
|
||||
<Button
|
||||
variant="secondary"
|
||||
size="sm"
|
||||
onClick={() => testMutation.mutate({...})}
|
||||
title={t('notificationProviders.sendTest')}
|
||||
aria-label={t('notificationProviders.sendTest')}
|
||||
>
|
||||
```
|
||||
|
||||
**Fix (test — alternative):** Use structural locator:
|
||||
```typescript
|
||||
const sendTestButton = providerRow.locator('button').first();
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 4.5 P3: Document Pre-existing Failures
|
||||
|
||||
**Action:** File separate issues (not part of this PR) for:
|
||||
|
||||
1. **encryption-management.spec.ts** — ~7 unique test failures in `/security/encryption`. Likely UI rendering timing issues or flaky selectors. No code overlap with Telegram PR.
|
||||
|
||||
2. **auth-middleware-cascade.spec.ts** — All 6 tests fail × 3 browsers. Uses deprecated `waitUntil: 'networkidle'`, creates proxy hosts through fragile UI selectors (`getByLabel(/domain/i)`), and tests auth middleware cascade. Needs modernization pass for locators and waits.
|
||||
|
||||
---
|
||||
|
||||
## 5. Implementation Plan
|
||||
|
||||
### Phase 1: Unit Test Fixes (Immediate)
|
||||
|
||||
| Task | File | Lines | Complexity |
|
||||
|---|---|---|---|
|
||||
| Fix "submits provider test action" test | `Notifications.test.tsx` | L447-462 | Low |
|
||||
| Fix "shows error toast" test | `Notifications.test.tsx` | L569-582 | Low |
|
||||
| Add `saveBeforeTesting` guard unit test | `Notifications.test.tsx` | New | Low |
|
||||
|
||||
**Validation:** `cd frontend && npx vitest run src/pages/__tests__/Notifications.test.tsx`
|
||||
|
||||
### Phase 2: E2E Test Fixes — Core Regression
|
||||
|
||||
| Task | File | Lines | Complexity |
|
||||
|---|---|---|---|
|
||||
| Fix "should test notification provider" | `notifications.spec.ts` | L1085-1138 | Medium |
|
||||
| Fix "should show test success feedback" | `notifications.spec.ts` | L1142-1178 | Medium |
|
||||
| Fix "should preserve Discord payload contract" | `notifications.spec.ts` | L1236-1340 | Medium |
|
||||
| Fix "should show error when test fails" | `notifications.spec.ts` | L1665-1706 | Medium |
|
||||
| Fix "transformation strips gotify token" | `notifications-payload.spec.ts` | L264-312 | Medium |
|
||||
| Fix "retry split retryable/non-retryable" | `notifications-payload.spec.ts` | L410-510 | High |
|
||||
|
||||
**Validation per test:** `npx playwright test --project=firefox <spec-file> -g "<test-name>"`
|
||||
|
||||
### Phase 3: Telegram Spec Hardening
|
||||
|
||||
| Task | File | Lines | Complexity |
|
||||
|---|---|---|---|
|
||||
| Replace keyboard nav with direct locator | `telegram-notification-provider.spec.ts` | L220-223 | Low |
|
||||
| Add `aria-label` to row Send Test button | `Notifications.tsx` | L703-708 | Low |
|
||||
| Verify all 8 telegram tests pass 3 browsers | All | — | Low |
|
||||
|
||||
**Validation:** `npx playwright test tests/settings/telegram-notification-provider.spec.ts`
|
||||
|
||||
### Phase 4: Accessibility Hardening (Optional — Low Priority)
|
||||
|
||||
Consider adding `aria-label` attributes to all icon-only buttons in the provider row for improved accessibility and test resilience:
|
||||
|
||||
| Button | Current Accessible Name Source | Recommended |
|
||||
|---|---|---|
|
||||
| Send Test | `title` attribute | Add `aria-label` |
|
||||
| Edit | None (icon only) | Add `aria-label={t('common.edit')}` |
|
||||
| Delete | None (icon only) | Add `aria-label={t('common.delete')}` |
|
||||
|
||||
---
|
||||
|
||||
## 6. Commit Slicing Strategy
|
||||
|
||||
**Decision:** Single PR with 2 focused commits
|
||||
|
||||
**Rationale:** All fixes are tightly coupled to the Telegram feature PR and represent test adaptations to a correct behavioral change. No cross-domain changes. Small total diff.
|
||||
|
||||
### Commit 1: "fix(test): adapt notification tests to save-before-test guard"
|
||||
- **Scope:** All unit test and E2E test fixes (Phases 1-3)
|
||||
- **Files:** `Notifications.test.tsx`, `notifications.spec.ts`, `notifications-payload.spec.ts`, `telegram-notification-provider.spec.ts`
|
||||
- **Dependencies:** None
|
||||
- **Validation Gate:** All notification-related tests pass locally on at least one browser
|
||||
|
||||
### Commit 2: "feat(a11y): add aria-labels to notification provider row buttons"
|
||||
- **Scope:** Source code accessibility improvement (Phase 4)
|
||||
- **Files:** `Notifications.tsx`
|
||||
- **Dependencies:** Depends on Commit 1 (tests must pass first)
|
||||
- **Validation Gate:** Telegram spec tests pass consistently on WebKit
|
||||
|
||||
### Rollback
|
||||
- These are test-only changes (except the optional aria-label). Reverting either commit has zero production impact.
|
||||
- If tests still fail after fixes, the next step is to run with `--debug` and capture trace artifacts.
|
||||
|
||||
---
|
||||
|
||||
## 7. Acceptance Criteria
|
||||
|
||||
- [ ] `Notifications.test.tsx` — all 2 previously failing tests pass
|
||||
- [ ] `notifications.spec.ts` — all 4 isNew-guard-affected tests pass on 3 browsers
|
||||
- [ ] `notifications-payload.spec.ts` — "transformation" and "retry split" tests pass on 3 browsers
|
||||
- [ ] `telegram-notification-provider.spec.ts` — all 8 tests pass on 3 browsers
|
||||
- [ ] No regressions in other notification tests
|
||||
- [ ] New unit test validates the `saveBeforeTesting` guard / disabled button behavior
|
||||
- [ ] `encryption-management.spec.ts` and `auth-middleware-cascade.spec.ts` failures documented as separate issues (not blocked by this PR)
|
||||
File diff suppressed because it is too large
Load Diff
43
docs/reports/qa_report_ts6_upgrade_2026-03-11.md
Normal file
43
docs/reports/qa_report_ts6_upgrade_2026-03-11.md
Normal file
@@ -0,0 +1,43 @@
|
||||
# QA Report: TypeScript 6.0 Upgrade
|
||||
|
||||
**Date**: 2026-03-11
|
||||
**Branch**: Current working branch
|
||||
**Scope**: TypeScript 5.9.3 → 6.0.1-rc upgrade (dev-dependency and config change only)
|
||||
|
||||
## Changes Under Test
|
||||
|
||||
- TypeScript bumped from 5.9.3 to 6.0.1-rc in root and frontend `package.json`
|
||||
- `tsconfig.json`: `types: []` added, `DOM.Iterable` removed from `lib`
|
||||
- `global.ResizeObserver` → `globalThis.ResizeObserver` in two test files
|
||||
- `@typescript-eslint/utils` moved from dependencies to devDependencies
|
||||
- Root `typescript` and `vite` moved from dependencies to devDependencies
|
||||
- npm `overrides` added for typescript-eslint peer dep resolution
|
||||
|
||||
## Results
|
||||
|
||||
| # | Check | Status | Details |
|
||||
|---|-------|--------|---------|
|
||||
| 1 | Type Safety (`tsc --noEmit`) | **PASS** | Zero errors. Clean compilation with TS 6.0.1-rc. |
|
||||
| 2 | Frontend Lint (ESLint) | **PASS** | 0 errors, 857 warnings. All warnings are pre-existing (testing-library, unicorn, security rules). No new warnings introduced. |
|
||||
| 3 | Frontend Unit Tests (Vitest) | **PASS** | 158 test files passed, 5 skipped. 1871 tests passed, 90 skipped. Zero failures. |
|
||||
| 4 | Frontend Build (Vite) | **PASS** | Production build completed in 9.23s. 2455 modules transformed. Output: 1,434 kB JS (420 kB gzip), 83 kB CSS. |
|
||||
| 5 | Pre-commit Hooks (Lefthook) | **PASS** | All 6 hooks passed: check-yaml, actionlint, end-of-file-fixer, trailing-whitespace, dockerfile-check, shellcheck. Frontend-specific hooks skipped (no staged files). |
|
||||
| 6 | Security Audit (`npm audit --omit=dev`) | **PASS** | 0 vulnerabilities in both frontend and root packages. The `overrides` field introduces no security regressions. |
|
||||
|
||||
## Pre-existing Items (Not Blocking)
|
||||
|
||||
- **857 ESLint warnings**: Existing `testing-library/no-node-access`, `unicorn/no-useless-undefined`, `security/detect-unsafe-regex`, and similar warnings. Not introduced by this change.
|
||||
- **5 skipped test files / 90 skipped tests**: Pre-existing skipped tests (Security.test.tsx suite and others). Not related to this change.
|
||||
|
||||
## Scans Skipped (Out of Scope)
|
||||
|
||||
- **GORM Security Scan**: No model/database changes.
|
||||
- **CodeQL Go Scan**: No Go code changed.
|
||||
- **Docker Image Security Scan**: Prep work only, not a deployable change.
|
||||
- **E2E Playwright**: Dev-dependency change does not affect runtime behavior.
|
||||
|
||||
## Verdict
|
||||
|
||||
**PASS**
|
||||
|
||||
All six verification checks passed with zero new errors, zero new warnings, and zero security vulnerabilities. The TypeScript 6.0.1-rc upgrade is safe to proceed.
|
||||
Reference in New Issue
Block a user