test(12): close Nyquist validation gap

This commit is contained in:
curo1305
2026-06-22 06:44:33 +02:00
parent e64980af5f
commit 692600c755
2 changed files with 124 additions and 14 deletions
@@ -5,7 +5,7 @@ status: complete
nyquist_compliant: true nyquist_compliant: true
wave_0_complete: true wave_0_complete: true
created: 2026-06-18 created: 2026-06-18
last_audited: 2026-06-19 last_audited: 2026-06-22
--- ---
# Phase 12 — Validation Strategy # Phase 12 — Validation Strategy
@@ -20,10 +20,10 @@ last_audited: 2026-06-19
|----------|-------| |----------|-------|
| **Framework** | pytest 9.x, Vitest 4.x, Vue Test Utils 2.x | | **Framework** | pytest 9.x, Vitest 4.x, Vue Test Utils 2.x |
| **Config file** | `backend/pytest.ini`, `frontend/vitest.config.js` | | **Config file** | `backend/pytest.ini`, `frontend/vitest.config.js` |
| **Quick backend command** | `docker compose run --rm backend pytest -q tests/test_cloud.py tests/test_cloud_backends.py tests/test_cloud_capabilities.py tests/test_cloud_items.py tests/test_cloud_security.py` | | **Quick backend command** | `docker compose run --rm backend pytest -q tests/test_cloud.py tests/test_cloud_backends.py tests/test_cloud_capabilities.py tests/test_cloud_items.py tests/test_cloud_security.py tests/test_compose_migrations.py tests/test_migration_0006.py` |
| **Quick frontend command** | `cd frontend && npm test -- --run src/components/storage/__tests__/StorageBrowser.capabilities.test.js src/components/ui/__tests__/BreadcrumbBar.test.js src/views/__tests__/CloudFolderView.test.js src/views/__tests__/CloudStorageView.test.js src/stores/__tests__/cloudConnections.test.js src/components/settings/__tests__/SettingsCloudTab.test.js` | | **Quick frontend command** | `cd frontend && npm test -- --run src/components/storage/__tests__/StorageBrowser.capabilities.test.js src/components/ui/__tests__/BreadcrumbBar.test.js src/views/__tests__/CloudFolderView.test.js src/views/__tests__/CloudStorageView.test.js src/stores/__tests__/cloudConnections.test.js src/components/settings/__tests__/SettingsCloudTab.test.js src/components/cloud/__tests__/CloudCredentialModal.test.js src/components/cloud/__tests__/CloudProviderTreeItem.test.js src/components/cloud/__tests__/CloudFolderTreeItem.test.js src/components/layout/__tests__/AppSidebar.test.js` |
| **Full suite command** | `cd backend && pytest -v`; `cd frontend && npm test`; `cd frontend && npm run build` | | **Full suite command** | `cd backend && pytest -v`; `cd frontend && npm test`; `cd frontend && npm run build` |
| **Fresh focused runtime** | Backend 155 tests in 4.14 s; frontend 62 tests in 703 ms (2026-06-19) | | **Fresh focused runtime** | Backend cloud/migration slice 159 passed, 12 prerequisite skips in 3.48 s plus 7 Compose contract tests in 0.23 s; frontend focused slice 76 passed in 1.12 s (2026-06-22) |
--- ---
@@ -52,6 +52,12 @@ last_audited: 2026-06-19
| 12-04-01 | 04 | 4 | All Phase 12 | T-12-01..10 | Dedicated owner/admin/credential/SSRF/no-byte negatives close cross-layer threats | integration/regression | `tests/test_cloud_security.py` plus focused suites | ✅ | ✅ green | | 12-04-01 | 04 | 4 | All Phase 12 | T-12-01..10 | Dedicated owner/admin/credential/SSRF/no-byte negatives close cross-layer threats | integration/regression | `tests/test_cloud_security.py` plus focused suites | ✅ | ✅ green |
| 12-04-02 | 04 | 4 | CONN-04, CLOUD-08 | T-12-SC | Static/dependency security gates have no unresolved Phase 12 blocker | security gate | `SECURITY.md` Phase 12 evidence | ✅ | ✅ green | | 12-04-02 | 04 | 4 | CONN-04, CLOUD-08 | T-12-SC | Static/dependency security gates have no unresolved Phase 12 blocker | security gate | `SECURITY.md` Phase 12 evidence | ✅ | ✅ green |
| 12-04-03 | 04 | 4 | All Phase 12 | — | Documentation/version claims match tracked shipped behavior | source + suites | `12-VERIFICATION.md`, summaries, version source assertions | ✅ | ✅ green | | 12-04-03 | 04 | 4 | All Phase 12 | — | Documentation/version claims match tracked shipped behavior | source + suites | `12-VERIFICATION.md`, summaries, version source assertions | ✅ | ✅ green |
| 12-05-01 | 05 | 5 | CONN-04, CLOUD-01 | T-12-05-01..04 | Exactly one hardened migrate service gates backend/worker/beat startup | compose contract | `tests/test_compose_migrations.py`; `docker compose config --quiet` | ✅ | ✅ green |
| 12-05-02 | 05 | 5 | CONN-04, CLOUD-01 | T-12-05-01, T-12-05-02 | A disposable PostgreSQL database upgrades 0005→0006 and preserves DDL separation | PostgreSQL integration | `tests/test_migration_0006.py` (12 tests; explicit prerequisite skip without disposable DSN) | ✅ | ✅ green |
| 12-05-03 | 05 | 5 | CONN-04, CLOUD-01 | T-12-05-03, T-12-05-04 | Cloud APIs run against migrated schema without credential disclosure | API regression + config | `tests/test_cloud.py`, `tests/test_cloud_security.py`, Compose config validation | ✅ | ✅ green |
| 12-06-01 | 06 | 6 | CONN-04, CLOUD-01 | T-12-06-01..05 | Same-provider accounts create independently; edits target one owned UUID; errors stay actionable and credential-free | API + component | `tests/test_cloud.py`, `tests/test_cloud_security.py`, `SettingsCloudTab.test.js`, `CloudCredentialModal.test.js` | ✅ | ✅ green |
| 12-06-02 | 06 | 6 | CLOUD-01, CLOUD-08 | T-12-06-06 | Sidebar browse/routes use connection UUIDs; breadcrumbs and deepest-node active state survive refresh failure | component/view | `CloudProviderTreeItem.test.js`, `CloudFolderTreeItem.test.js`, `AppSidebar.test.js`, `CloudFolderView.test.js`, `CloudStorageView.test.js` | ✅ | ✅ green |
| 12-06-03 | 06 | 6 | CONN-04, CLOUD-01, CLOUD-08 | T-12-06-SC | UAT regressions, version alignment, build, and security gates remain release-ready | cross-layer suite | Phase summaries/security evidence plus focused and full-suite commands | ✅ | ✅ green |
*Status: ✅ green · ❌ red · ⚠️ partial/manual* *Status: ✅ green · ❌ red · ⚠️ partial/manual*
@@ -61,8 +67,8 @@ last_audited: 2026-06-19
| Requirement | Status | Automated evidence | | Requirement | Status | Automated evidence |
|-------------|--------|--------------------| |-------------|--------|--------------------|
| CONN-04 | COVERED | Capability contract tests, connection store/settings tests, StorageBrowser action-state tests | | CONN-04 | COVERED | Capability contract tests, independent same-provider lifecycle and owner-targeted edit tests, connection store/settings tests, StorageBrowser action-state tests |
| CLOUD-01 | COVERED | Connection-ID API tests and shared CloudStorageView/CloudFolderView/StorageBrowser tests | | CLOUD-01 | COVERED | Migration-gated startup tests, connection-ID API tests, UUID-only sidebar navigation, and shared CloudStorageView/CloudFolderView/StorageBrowser tests |
| CLOUD-08 | COVERED | Unsupported/temporarily-unavailable state, message, pointer, Enter, Space, and touch suppression tests | | CLOUD-08 | COVERED | Unsupported/temporarily-unavailable state, message, pointer, Enter, Space, and touch suppression tests |
| CACHE-01 | COVERED | No-MinIO, no-byte-download, no-quota-mutation browse/refresh tests | | CACHE-01 | COVERED | No-MinIO, no-byte-download, no-quota-mutation browse/refresh tests |
| CACHE-02 | COVERED | Cloud metadata/extracted text/topic/semantic fields and cross-connection isolation tests | | CACHE-02 | COVERED | Cloud metadata/extracted text/topic/semantic fields and cross-connection isolation tests |
@@ -77,7 +83,10 @@ last_audited: 2026-06-19
- [x] Database constraint/isolation coverage for UUID ownership, unique connection/item identity, and cascade behavior. - [x] Database constraint/isolation coverage for UUID ownership, unique connection/item identity, and cascade behavior.
- [x] `CloudFolderView.test.js` and `CloudStorageView.test.js` — thin shared-browser view integration. - [x] `CloudFolderView.test.js` and `CloudStorageView.test.js` — thin shared-browser view integration.
- [x] Provider response fixtures for Google Drive, OneDrive, Nextcloud, and generic WebDAV normalization. - [x] Provider response fixtures for Google Drive, OneDrive, Nextcloud, and generic WebDAV normalization.
- [x] Focused runtime baseline recorded: backend 4.14 s, frontend 703 ms, excluding container startup. - [x] `test_compose_migrations.py` and `test_migration_0006.py` — startup gate and disposable PostgreSQL 0005→0006 regression.
- [x] `CloudCredentialModal.test.js` — new-vs-edit endpoint selection, connection-ID targeting, omitted-password preservation, and actionable errors.
- [x] Connection tree/sidebar tests — UUID-only routes, nested folder selection, and exact overview active state.
- [x] Fresh focused runtime recorded for backend, frontend, and Compose contract slices.
--- ---
@@ -92,23 +101,32 @@ These checks supplement automated coverage; no Phase 12 requirement depends sole
--- ---
## Validation Audit 2026-06-19 ## Validation Audit 2026-06-22
| Metric | Count | | Metric | Count |
|--------|-------| |--------|-------|
| Phase requirements audited | 6 | | Phase requirements audited | 6 |
| Automated requirements covered | 6 | | Automated requirements covered | 6 |
| Gaps found | 0 | | Executed plans audited | 6 |
| Tests generated | 0 | | Gaps found | 1 |
| Tests generated | 1 file / 3 tests |
| Gaps escalated to manual-only | 0 | | Gaps escalated to manual-only | 0 |
| Supplementary manual visual checks retained | 2 | | Supplementary manual visual checks retained | 2 |
### Gap Closed
- Plan 12-06 named `frontend/src/components/cloud/__tests__/CloudCredentialModal.test.js` as required automated evidence, but the file was absent; only a mobile-layout test existed.
- Generated the missing functional suite with three regressions: fresh Nextcloud creation builds the WebDAV URL and calls create, editing targets the selected connection UUID while omitting a blank password, and API validation detail remains visible without closing the modal.
### Fresh Execution Evidence ### Fresh Execution Evidence
- Backend focused cloud suite: **155 passed**, 1 non-failing SlowAPI deprecation warning, 4.14 s. - Backend cloud/migration focused slice: **159 passed, 12 skipped**, 1 non-failing SlowAPI deprecation warning, 3.48 s. The 12 migration integration cases correctly require an explicitly disposable PostgreSQL DSN and did not touch the development database.
- Frontend focused cloud/UI suite: **62 passed** across 6 files, 703 ms. - Compose migration contract: **7 passed** in 0.23 s with the repository Compose file mounted read-only into the backend test container.
- Frontend focused suite after gap closure: **76 passed** across 10 files, 1.12 s.
- Compose environment contract: `docker compose config --quiet` passed with required variables resolved.
- Execution closeout evidence: backend 509 passed with one documented environment-only `libmagic` failure; frontend 323 passed; production build succeeded. - Execution closeout evidence: backend 509 passed with one documented environment-only `libmagic` failure; frontend 323 passed; production build succeeded.
- Phase verification: `.planning/phases/12-cloud-resource-foundation/12-VERIFICATION.md` status `passed`, 12/12 must-haves verified with one documented package-lock tracking override. - Later Plan 12-06 closeout evidence: backend 516 passed (7 xfailed, 17 skipped), frontend 334 passed across 42 files, and production build succeeded.
- Phase verification: `.planning/phases/12-cloud-resource-foundation/12-VERIFICATION.md` status `passed`; versions align at 0.2.2.
--- ---
@@ -122,4 +140,4 @@ These checks supplement automated coverage; no Phase 12 requirement depends sole
- [x] Focused feedback latency is below 60 seconds. - [x] Focused feedback latency is below 60 seconds.
- [x] `nyquist_compliant: true` is set in frontmatter. - [x] `nyquist_compliant: true` is set in frontmatter.
**Approval:** approved 2026-06-19 **Approval:** approved 2026-06-22
@@ -0,0 +1,92 @@
import { describe, it, expect, vi, beforeEach } from 'vitest'
import { mount, flushPromises } from '@vue/test-utils'
vi.mock('../../../api/client.js', () => ({
getConnectionConfig: vi.fn(),
connectWebDav: vi.fn(),
updateWebDavCredentials: vi.fn(),
}))
import * as api from '../../../api/client.js'
import CloudCredentialModal from '../CloudCredentialModal.vue'
const nextcloudProvider = { key: 'nextcloud', label: 'Nextcloud' }
const global = { stubs: { AppIcon: true } }
async function openModal(props = {}) {
const wrapper = mount(CloudCredentialModal, {
props: {
show: false,
provider: nextcloudProvider,
existing: null,
...props,
},
global,
})
await wrapper.setProps({ show: true })
await flushPromises()
return wrapper
}
beforeEach(() => {
vi.clearAllMocks()
api.getConnectionConfig.mockResolvedValue({
connection_username: 'alice',
server_url: 'https://cloud.example.com/remote.php/dav/files/alice/',
})
api.connectWebDav.mockResolvedValue({ id: 'new-connection-id' })
api.updateWebDavCredentials.mockResolvedValue({ id: 'connection-id' })
})
describe('CloudCredentialModal connection lifecycle', () => {
it('creates a new Nextcloud connection and constructs its WebDAV URL', async () => {
const wrapper = await openModal()
await wrapper.find('input[type="url"]').setValue('https://cloud.example.com')
await wrapper.find('input[type="text"]').setValue('alice@example.com')
await wrapper.find('input[type="password"]').setValue('app-password')
await wrapper.find('form').trigger('submit')
await flushPromises()
expect(api.connectWebDav).toHaveBeenCalledWith(
'nextcloud',
'https://cloud.example.com/remote.php/dav/files/alice%40example.com/',
'alice@example.com',
'app-password',
)
expect(api.updateWebDavCredentials).not.toHaveBeenCalled()
expect(wrapper.emitted('connected')).toHaveLength(1)
expect(wrapper.emitted('close')).toHaveLength(1)
})
it('updates the selected connection ID and omits a blank replacement password', async () => {
const wrapper = await openModal({ existing: { id: 'connection-id' } })
expect(api.getConnectionConfig).toHaveBeenCalledWith('connection-id')
expect(wrapper.find('input[type="text"]').element.value).toBe('alice')
await wrapper.find('form').trigger('submit')
await flushPromises()
expect(api.updateWebDavCredentials).toHaveBeenCalledWith('connection-id', {
serverUrl: 'https://cloud.example.com/remote.php/dav/files/alice/',
username: 'alice',
password: undefined,
})
expect(api.connectWebDav).not.toHaveBeenCalled()
})
it('shows an actionable API validation message without closing', async () => {
api.connectWebDav.mockRejectedValueOnce(new Error('server_url: URL must use HTTPS'))
const wrapper = await openModal()
await wrapper.find('input[type="url"]').setValue('http://cloud.example.com')
await wrapper.find('input[type="text"]').setValue('alice')
await wrapper.find('input[type="password"]').setValue('secret')
await wrapper.find('form').trigger('submit')
await flushPromises()
expect(wrapper.text()).toContain('server_url: URL must use HTTPS')
expect(wrapper.emitted('connected')).toBeUndefined()
expect(wrapper.emitted('close')).toBeUndefined()
})
})