Test website protected routes and shell behavior
This commit is contained in:
parent
57c1f1ed97
commit
2d04d62a2c
5 changed files with 156 additions and 3 deletions
|
|
@ -154,6 +154,7 @@ The frontend behavior coverage now also explicitly pins:
|
|||
- safe `next`-path normalization across custom auth pages and SuperTokens post-auth redirect handoff
|
||||
- browser auth-bootstrap normalization when the account payload reports email/fallback posture
|
||||
- login/register page continuation behavior plus the public `/download` page rule that configured release targets still route through the protected dashboard instead of exposing raw URLs
|
||||
- protected app-route loading/redirect behavior plus auth-aware marketing/app shell actions
|
||||
- dashboard launch-readiness visibility plus generated desktop-link verify URL behavior
|
||||
|
||||
The first-party auth server now also supports bounded same-origin public serving
|
||||
|
|
|
|||
|
|
@ -264,7 +264,7 @@ repo.
|
|||
| Feature | Status | Primary authority | Notes |
|
||||
|---|---|---|---|
|
||||
| Public `hypertwist.app` marketing shell | Implemented now | first-party `website/` app + feature registry/roadmap authority | HyperTwist now has a dedicated first-party public web surface for homepage, about, resources, pricing, download, support, and legal routes. This lane is separate from the embedded Unreal browser runtime under `Content/Browser/` and does not claim browser-simulator parity. The same package now also carries a first-party external runtime-readiness verifier so deploy-time env and live health posture can be checked outside the dashboard, plus separated local-versus-production env templates whose placeholder values are intentionally rejected until real launch config is in place, bootstrap CI now validates both the frontend and auth-server website commands directly, and the auth server can now auto-serve the built `website/dist` bundle with bounded SPA fallback for same-origin public deployment. Request-level server coverage now also proves that public/app shell delivery does not shadow `/api/*`, `/auth*`, `/health`, or missing asset paths. |
|
||||
| Browser-based operator/account dashboard | Implemented now | first-party `website/` app + shared auth/dashboard packet | A protected browser dashboard is now live for operator access, account state, download posture, browser-access boundary explanation, notices review, and bounded billing/entitlement status. It reuses the shared SuperTokens auth posture proven in FamiliarOS and ScriptoriumAI while remaining HyperTwist-specific in product content and boundary claims, the current auth-health surface now truthfully distinguishes configured versus reachable or ready shared-core posture while exposing fallback-active reason instead of hardcoding readiness, and the same dashboard now also surfaces launch-readiness truth for download URLs, checkout links, source/notices URLs, billing-secret/map configuration, and local-versus-public runtime deployment posture. Focused frontend coverage now also protects deep-link login redirect preservation, safe `next`-path normalization across auth entry points, fallback/email auth-bootstrap normalization, login/register continuation behavior, public download-gating behavior, and desktop-link verify-url/dashboard readiness behavior. |
|
||||
| Browser-based operator/account dashboard | Implemented now | first-party `website/` app + shared auth/dashboard packet | A protected browser dashboard is now live for operator access, account state, download posture, browser-access boundary explanation, notices review, and bounded billing/entitlement status. It reuses the shared SuperTokens auth posture proven in FamiliarOS and ScriptoriumAI while remaining HyperTwist-specific in product content and boundary claims, the current auth-health surface now truthfully distinguishes configured versus reachable or ready shared-core posture while exposing fallback-active reason instead of hardcoding readiness, and the same dashboard now also surfaces launch-readiness truth for download URLs, checkout links, source/notices URLs, billing-secret/map configuration, and local-versus-public runtime deployment posture. Focused frontend coverage now also protects deep-link login redirect preservation, safe `next`-path normalization across auth entry points, fallback/email auth-bootstrap normalization, login/register continuation behavior, public download-gating behavior, protected-route/shell behavior, and desktop-link verify-url/dashboard readiness behavior. |
|
||||
| Desktop download posture and browser-to-desktop pairing | Implemented now | first-party `website/` app + `website/server` desktop-link endpoints | Public download targets, dashboard-side release posture, and short-lived desktop-link token generation/verification are now first-party owned. The current server posture now enforces exact website-origin matching, bounded per-user issuance, one-time token consumption, and billing-backed plan/download entitlement resolution with focused `website/server` tests green on `2026-06-22`, and the verify handshake now returns the same resolved download-entitlement posture the dashboard sees instead of only identity plus plan/role. The public `/download` page now keeps raw download URLs behind the protected dashboard instead of exposing them directly. Actual release URLs remain deployment configuration rather than hardcoded product truth. |
|
||||
| Paddle-ready pricing and billing webhook seam | Implemented now | first-party `website/` app + `website/server` billing endpoint | The public pricing surface now exists with plan structure, checkout-link configuration seams, and the same `/api/billing/paddle/webhook` endpoint family used by the broader product website lane. The current server now verifies `Paddle-Signature` against `PADDLE_WEBHOOK_SECRET` using the documented raw-body HMAC flow, persists a bounded first-party billing state file, and applies verified Paddle events into account/download entitlement state that the browser dashboard consumes, with focused `website/server` tests green on `2026-06-22`. Production checkout URLs, secret management, and broader operator/admin billing workflows remain deployment/application tasks, not shipped-code omissions. |
|
||||
| Public open-source notices and corresponding-source surface | Implemented now | first-party `website/` app + `HYPERTWIST_MPL_DISTRIBUTION_PLACEMENT_CHECKLIST_2026-05-25.md` | HyperTwist now has a stable public `Open Source Notices` route linked from pricing, download, and footer surfaces, satisfying the requirement that public distribution surfaces expose notice and corresponding-source guidance when shipped builds contain `MPL`-covered material. The exact public corresponding-source URL still must be configured before external launch. |
|
||||
|
|
|
|||
|
|
@ -222,8 +222,8 @@ Current consolidated milestone snapshot:
|
|||
focused frontend coverage now also pins deep-link login redirects, safe
|
||||
`next`-path normalization, fallback auth-bootstrap normalization,
|
||||
login/register continuation behavior, public download-gating behavior, and
|
||||
dashboard launch-readiness plus desktop-link verify-url behavior, and the
|
||||
auth server can now auto-serve the built
|
||||
protected-route plus shell-action behavior, and dashboard launch-readiness
|
||||
plus desktop-link verify-url behavior, and the auth server can now auto-serve the built
|
||||
`website/dist` bundle with bounded SPA fallback for same-origin `hypertwist.app`
|
||||
deployment when that build output is present, while the env templates and
|
||||
runtime-readiness verifier now also make that static-serving posture explicit
|
||||
|
|
|
|||
|
|
@ -96,4 +96,5 @@ The focused frontend test coverage now also pins:
|
|||
- safe `next`-path normalization across custom auth pages and SuperTokens redirect handoff
|
||||
- auth-bootstrap normalization when fallback/email sessions are re-hydrated
|
||||
- login/register page continuation behavior and protected-dashboard download gating on the public download page
|
||||
- protected-route loading/redirect behavior plus auth-aware marketing/app shell actions
|
||||
- dashboard launch-readiness plus desktop-link verify-url behavior
|
||||
|
|
|
|||
151
website/src/__tests__/route-shells.test.tsx
Normal file
151
website/src/__tests__/route-shells.test.tsx
Normal file
|
|
@ -0,0 +1,151 @@
|
|||
import { cleanup, render, screen } from '@testing-library/react'
|
||||
import userEvent from '@testing-library/user-event'
|
||||
import { MemoryRouter, Route, Routes, useLocation } from 'react-router-dom'
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { ROUTER_FUTURE_FLAGS } from '../router/router-future'
|
||||
|
||||
const mockUsePlatformAuth = vi.fn()
|
||||
const mockLogout = vi.fn()
|
||||
const mockToggleColorMode = vi.fn()
|
||||
|
||||
vi.mock('../auth/platform-auth', () => ({
|
||||
usePlatformAuth: () => mockUsePlatformAuth(),
|
||||
}))
|
||||
|
||||
import { MarketingShell } from '../components/layout/MarketingShell'
|
||||
import { AppShell } from '../components/layout/AppShell'
|
||||
import { ProtectedRoute } from '../components/routes/ProtectedRoute'
|
||||
|
||||
function LocationEcho() {
|
||||
const location = useLocation()
|
||||
return <div data-testid="location">{location.pathname}{location.search}{location.hash}</div>
|
||||
}
|
||||
|
||||
function renderProtectedRoute(initialEntry: string) {
|
||||
return render(
|
||||
<MemoryRouter initialEntries={[initialEntry]} future={ROUTER_FUTURE_FLAGS}>
|
||||
<Routes>
|
||||
<Route element={<ProtectedRoute />}>
|
||||
<Route path="/app/account" element={<div>Account page</div>} />
|
||||
</Route>
|
||||
<Route path="/login" element={<LocationEcho />} />
|
||||
</Routes>
|
||||
</MemoryRouter>,
|
||||
)
|
||||
}
|
||||
|
||||
describe('website route and shell behavior', () => {
|
||||
beforeEach(() => {
|
||||
cleanup()
|
||||
mockUsePlatformAuth.mockReset()
|
||||
mockLogout.mockReset()
|
||||
mockToggleColorMode.mockReset()
|
||||
mockUsePlatformAuth.mockReturnValue({
|
||||
isAuthenticated: false,
|
||||
isLoading: false,
|
||||
logout: () => mockLogout(),
|
||||
toggleColorMode: () => mockToggleColorMode(),
|
||||
colorMode: 'dark',
|
||||
user: {
|
||||
name: 'Operator',
|
||||
plan: 'operator',
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
it('shows the protected-route loader while auth state is still resolving', () => {
|
||||
mockUsePlatformAuth.mockReturnValue({
|
||||
isAuthenticated: false,
|
||||
isLoading: true,
|
||||
})
|
||||
|
||||
renderProtectedRoute('/app/account')
|
||||
|
||||
expect(screen.getByText('Loading operator access...')).toBeTruthy()
|
||||
})
|
||||
|
||||
it('redirects unauthenticated protected routes to login with the full encoded next target', async () => {
|
||||
renderProtectedRoute('/app/account?tab=billing#security')
|
||||
|
||||
expect((await screen.findByTestId('location')).textContent).toBe('/login?next=%2Fapp%2Faccount%3Ftab%3Dbilling%23security')
|
||||
})
|
||||
|
||||
it('renders the protected outlet when authentication is present', () => {
|
||||
mockUsePlatformAuth.mockReturnValue({
|
||||
isAuthenticated: true,
|
||||
isLoading: false,
|
||||
})
|
||||
|
||||
renderProtectedRoute('/app/account')
|
||||
|
||||
expect(screen.getByText('Account page')).toBeTruthy()
|
||||
})
|
||||
|
||||
it('switches the marketing-shell auth action between login and dashboard', () => {
|
||||
const { rerender } = render(
|
||||
<MemoryRouter future={ROUTER_FUTURE_FLAGS}>
|
||||
<MarketingShell eyebrow="Eyebrow" title="Title" lede="Lede">
|
||||
<div>Body</div>
|
||||
</MarketingShell>
|
||||
</MemoryRouter>,
|
||||
)
|
||||
|
||||
expect(screen.getByRole('link', { name: 'Log in' }).getAttribute('href')).toBe('/login')
|
||||
|
||||
mockUsePlatformAuth.mockReturnValue({
|
||||
isAuthenticated: true,
|
||||
isLoading: false,
|
||||
logout: () => mockLogout(),
|
||||
toggleColorMode: () => mockToggleColorMode(),
|
||||
colorMode: 'dark',
|
||||
user: {
|
||||
name: 'Operator',
|
||||
plan: 'operator',
|
||||
},
|
||||
})
|
||||
|
||||
rerender(
|
||||
<MemoryRouter future={ROUTER_FUTURE_FLAGS}>
|
||||
<MarketingShell eyebrow="Eyebrow" title="Title" lede="Lede">
|
||||
<div>Body</div>
|
||||
</MarketingShell>
|
||||
</MemoryRouter>,
|
||||
)
|
||||
|
||||
expect(screen.getByRole('link', { name: 'Open dashboard' }).getAttribute('href')).toBe('/app')
|
||||
})
|
||||
|
||||
it('shows operator identity in the app shell and wires theme/logout actions', async () => {
|
||||
mockUsePlatformAuth.mockReturnValue({
|
||||
isAuthenticated: true,
|
||||
isLoading: false,
|
||||
logout: () => mockLogout(),
|
||||
toggleColorMode: () => mockToggleColorMode(),
|
||||
colorMode: 'dark',
|
||||
user: {
|
||||
name: 'Operator Prime',
|
||||
plan: 'studio',
|
||||
},
|
||||
})
|
||||
|
||||
render(
|
||||
<MemoryRouter initialEntries={['/app']} future={ROUTER_FUTURE_FLAGS}>
|
||||
<Routes>
|
||||
<Route path="/app" element={<AppShell />}>
|
||||
<Route index element={<div>Overview content</div>} />
|
||||
</Route>
|
||||
</Routes>
|
||||
</MemoryRouter>,
|
||||
)
|
||||
|
||||
expect(screen.getByText('Operator Prime')).toBeTruthy()
|
||||
expect(screen.getByText('studio plan')).toBeTruthy()
|
||||
expect(screen.getByText('Overview content')).toBeTruthy()
|
||||
|
||||
await userEvent.click(screen.getByRole('button', { name: 'Switch to light mode' }))
|
||||
await userEvent.click(screen.getByRole('button', { name: 'Log out' }))
|
||||
|
||||
expect(mockToggleColorMode).toHaveBeenCalledTimes(1)
|
||||
expect(mockLogout).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
})
|
||||
Loading…
Add table
Reference in a new issue