test(12): close Nyquist validation gap
This commit is contained in:
@@ -5,7 +5,7 @@ status: complete
|
||||
nyquist_compliant: true
|
||||
wave_0_complete: true
|
||||
created: 2026-06-18
|
||||
last_audited: 2026-06-19
|
||||
last_audited: 2026-06-22
|
||||
---
|
||||
|
||||
# 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 |
|
||||
| **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 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 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 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` |
|
||||
| **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-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-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*
|
||||
|
||||
@@ -61,8 +67,8 @@ last_audited: 2026-06-19
|
||||
|
||||
| Requirement | Status | Automated evidence |
|
||||
|-------------|--------|--------------------|
|
||||
| CONN-04 | COVERED | Capability contract tests, connection store/settings tests, StorageBrowser action-state tests |
|
||||
| CLOUD-01 | COVERED | Connection-ID API tests and shared CloudStorageView/CloudFolderView/StorageBrowser 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 | 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 |
|
||||
| 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 |
|
||||
@@ -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] `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] 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 |
|
||||
|--------|-------|
|
||||
| Phase requirements audited | 6 |
|
||||
| Automated requirements covered | 6 |
|
||||
| Gaps found | 0 |
|
||||
| Tests generated | 0 |
|
||||
| Executed plans audited | 6 |
|
||||
| Gaps found | 1 |
|
||||
| Tests generated | 1 file / 3 tests |
|
||||
| Gaps escalated to manual-only | 0 |
|
||||
| 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
|
||||
|
||||
- Backend focused cloud suite: **155 passed**, 1 non-failing SlowAPI deprecation warning, 4.14 s.
|
||||
- Frontend focused cloud/UI suite: **62 passed** across 6 files, 703 ms.
|
||||
- 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.
|
||||
- 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.
|
||||
- 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] `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()
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user