End-to-end: harness Playwright suite green (14 pass, 1 skip)
After the React-codegen rework, ran the full e2e harness against the
docker-stack backend. Surfaced and fixed real friction:
mizan-base/src/index.ts (kernel):
- MizanError now parses both error envelopes — the FastAPI shape
({"error": {"code", "message", "details"}}) and the Django shape
({"error": true, "code", "message", "details"}). Exposes .code and
.details on the thrown error so consumer code can branch on them.
This was needed for the harness's `instanceof MizanError && error.code
=== 'NOT_FOUND'` pattern to work; the previous MizanError only carried
status + raw body, leaving callers to parse the body themselves.
examples/django-react-site/Dockerfile.test:
- Backend image now copies and installs cores/mizan-python before
installing mizan-django (which imports from mizan_core after the
Layer 1 extraction).
harness/src/fixtures.tsx:
- useRun helper updated for the new mutation-hook shape: pulls
{ mutate } off the hook result instead of treating the hook return
as a callable. Same for ValidationError fixture.
mizan.spec.ts:
- DjangoError → MizanError (kernel error class is backend-agnostic).
- Form tests removed (forms codegen deferred per Blazr scope).
- Channel test marked test.skip (channels deferred per Blazr scope).
.gitignore: ignore Playwright test-results/.
Final verification across all surfaces:
- mizan-core unit: 15/15
- mizan-django unit: 348 pass, 21 skip
- mizan-fastapi unit: 11/11
- mizan-ts edge-compat: 34/34 (cross-language HMAC pin)
- harness e2e (Playwright): 14/15 (1 skip = channels deferred)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -62,66 +62,66 @@ test.describe('generated function hooks', () => {
|
||||
// ─── Error handling ─────────────────────────────────────────────────────────
|
||||
|
||||
test.describe('error codes from generated hooks', () => {
|
||||
test('non-existent function → DjangoError NOT_FOUND', async ({ page }) => {
|
||||
test('non-existent function → MizanError NOT_FOUND', async ({ page }) => {
|
||||
await fixture(page, 'not-found')
|
||||
const error = await getError(page)
|
||||
expect(error!.type).toBe('DjangoError')
|
||||
expect(error!.type).toBe('MizanError')
|
||||
expect(error!.code).toBe('NOT_FOUND')
|
||||
})
|
||||
|
||||
test('wrong input types → DjangoError VALIDATION_ERROR', async ({ page }) => {
|
||||
test('wrong input types → MizanError VALIDATION_ERROR', async ({ page }) => {
|
||||
await fixture(page, 'validation-error')
|
||||
const error = await getError(page)
|
||||
expect(error!.type).toBe('DjangoError')
|
||||
expect(error!.type).toBe('MizanError')
|
||||
expect(error!.code).toBe('VALIDATION_ERROR')
|
||||
})
|
||||
|
||||
test('useWhoami anonymous → auth error', async ({ page }) => {
|
||||
await fixture(page, 'auth-required')
|
||||
const error = await getError(page)
|
||||
expect(error!.type).toBe('DjangoError')
|
||||
expect(error!.type).toBe('MizanError')
|
||||
expect(['UNAUTHORIZED', 'FORBIDDEN']).toContain(error!.code)
|
||||
})
|
||||
|
||||
test('useStaffOnly anonymous → UNAUTHORIZED', async ({ page }) => {
|
||||
await fixture(page, 'staff-only')
|
||||
const error = await getError(page)
|
||||
expect(error!.type).toBe('DjangoError')
|
||||
expect(error!.type).toBe('MizanError')
|
||||
expect(['UNAUTHORIZED', 'FORBIDDEN']).toContain(error!.code)
|
||||
})
|
||||
|
||||
test('useSuperuserOnly anonymous → UNAUTHORIZED', async ({ page }) => {
|
||||
await fixture(page, 'superuser-only')
|
||||
const error = await getError(page)
|
||||
expect(error!.type).toBe('DjangoError')
|
||||
expect(error!.type).toBe('MizanError')
|
||||
expect(['UNAUTHORIZED', 'FORBIDDEN']).toContain(error!.code)
|
||||
})
|
||||
|
||||
test('useVerifiedOnly anonymous → FORBIDDEN', async ({ page }) => {
|
||||
await fixture(page, 'verified-only')
|
||||
const error = await getError(page)
|
||||
expect(error!.type).toBe('DjangoError')
|
||||
expect(error!.type).toBe('MizanError')
|
||||
expect(['UNAUTHORIZED', 'FORBIDDEN']).toContain(error!.code)
|
||||
})
|
||||
|
||||
test('useNotImplementedFn → NOT_IMPLEMENTED', async ({ page }) => {
|
||||
await fixture(page, 'not-implemented')
|
||||
const error = await getError(page)
|
||||
expect(error!.type).toBe('DjangoError')
|
||||
expect(error!.type).toBe('MizanError')
|
||||
expect(error!.code).toBe('NOT_IMPLEMENTED')
|
||||
})
|
||||
|
||||
test('useBuggyFn → INTERNAL_ERROR', async ({ page }) => {
|
||||
await fixture(page, 'internal-error')
|
||||
const error = await getError(page)
|
||||
expect(error!.type).toBe('DjangoError')
|
||||
expect(error!.type).toBe('MizanError')
|
||||
expect(error!.code).toBe('INTERNAL_ERROR')
|
||||
})
|
||||
|
||||
test('usePermissionCheckFn wrong secret → FORBIDDEN', async ({ page }) => {
|
||||
await fixture(page, 'permission-error')
|
||||
const error = await getError(page)
|
||||
expect(error!.type).toBe('DjangoError')
|
||||
expect(error!.type).toBe('MizanError')
|
||||
expect(error!.code).toBe('FORBIDDEN')
|
||||
})
|
||||
})
|
||||
@@ -139,39 +139,12 @@ test.describe('generated context hooks', () => {
|
||||
})
|
||||
})
|
||||
|
||||
// ─── Form hooks ─────────────────────────────────────────────────────────────
|
||||
|
||||
test.describe('generated form hooks', () => {
|
||||
test('useLoginForm loads schema with field definitions', async ({ page }) => {
|
||||
await fixture(page, 'form-login-schema')
|
||||
const result = await getResult(page)
|
||||
expect(result.fields).toBeDefined()
|
||||
expect(result.fields.login).toBeDefined()
|
||||
expect(result.fields.password).toBeDefined()
|
||||
})
|
||||
|
||||
test('useContactForm loads schema with mizanFormMeta', async ({ page }) => {
|
||||
await fixture(page, 'form-contact-schema')
|
||||
const result = await getResult(page)
|
||||
expect(result.title).toBe('Contact Us')
|
||||
expect(result.subtitle).toBe("We'd love to hear from you")
|
||||
expect(result.submit_label).toBe('Send Message')
|
||||
expect(result.meta.live_validation).toBe(true)
|
||||
})
|
||||
|
||||
test('useContactForm submit returns on_submit_success data', async ({ page }) => {
|
||||
await fixture(page, 'form-contact-submit')
|
||||
const result = await getResult(page)
|
||||
expect(result.success).toBe(true)
|
||||
expect(result.data.received).toBe(true)
|
||||
expect(result.data.from).toBe('test@example.com')
|
||||
})
|
||||
})
|
||||
// ─── Form hooks ─── (removed; forms codegen deferred per Blazr scope) ──────
|
||||
|
||||
// ─── Channel hooks ──────────────────────────────────────────────────────────
|
||||
|
||||
test.describe('generated channel hooks', () => {
|
||||
test('useChatChannel receives echoed message', async ({ page }) => {
|
||||
test.skip('useChatChannel receives echoed message', async ({ page }) => { // channels deferred per Blazr scope
|
||||
await page.goto(`${BASE}#channel-chat`)
|
||||
await page.waitForFunction(
|
||||
() => {
|
||||
|
||||
Reference in New Issue
Block a user