src/features/account/account.test-utils.tsx
Rama main · réplica de lectura del repositorio original.
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { render } from '@testing-library/react'
import { HttpResponse, http } from 'msw'
import { setupServer } from 'msw/node'
import type { ReactNode } from 'react'
import { AccountCenterLayout } from './account-surface'
const base = 'http://localhost:3000'
export type AccountStore = ReturnType<typeof createAccountStore>
export function createAccountStore() {
return {
activeOrganizationId: null as string | null,
access: {
canCreateOrganization: true,
showOrganizations: false,
platformOperator: false,
consoleOrganizations: [] as Array<{ organizationId: string; level: string }>,
},
profile: {
id: 'user-1',
email: 'jane@example.com',
emailVerified: true,
displayName: 'Jane Stone',
username: 'jane' as string | null,
avatarAssetId: null as string | null,
image: null as string | null,
bio: null as string | null,
location: null as string | null,
links: [] as import('@shared/api/public-profiles').AccountProfileLink[],
role: 'user' as string | null,
},
security: {
mfa: { enabled: false, factors: [] as unknown[] },
passkeys: { enabled: true, count: 0 },
policy: { mfa: { mode: 'optional' }, passkeys: { enabled: true, rpName: 'Realmroot' } },
},
passkeys: [] as Array<{
id: string
name: string | null
deviceType: string
backedUp: boolean
createdAt: string | null
}>,
sessions: [] as Array<{
id: string
userAgent: string | null
ipAddress: string | null
expiresAt: string
current?: boolean
}>,
linkedAccounts: [] as Array<{
id: string
accountId: string
providerId: string
createdAt: string
}>,
applications: [] as Array<{
id: string
applicationName: string
scopes: string[]
grantedAt: string
expiresAt?: string | null
}>,
agents: [] as Array<{
id: string
name: string
status: string
host: { id: string; name: string | null }
capabilityGrants: Array<{ id: string; capability: string }>
}>,
agentIdentities: [] as Array<{
id: string
issuer: string
subject: string
name: string
homeSpace: { type: 'personal'; userId: string }
status: 'active' | 'inactive'
createdAt: string
updatedAt: string
bindings: Array<{
id: string
protocolAgentId: string
hostId: string
status: 'active' | 'revoked'
boundAt: string
revokedAt: string | null
}>
}>,
}
}
export function configz(overrides: Partial<ReturnType<typeof baseConfigz>> = {}) {
return { ...baseConfigz(), ...overrides }
}
function baseConfigz() {
return {
onboarding: { required: false, href: '/onboarding' },
signIn: {
passwordEnabled: true,
signupEnabled: true,
socialLoginEnabled: true,
emailOtpEnabled: true,
usernameEnabled: true,
identifierFirst: false,
},
builtInProviders: {
email: { enabled: true },
phone: { enabled: false },
web3Wallet: { enabled: true, allowSignUp: true, chains: [1] },
passkey: { allowSignUp: true },
oneTap: {
enabled: false,
clientId: '',
autoSelect: false,
cancelOnTapOutside: true,
uxMode: 'popup',
context: 'signin',
promptBaseDelayMs: 0,
promptMaxAttempts: 1,
},
},
branding: { logoUrl: null, faviconUrl: null, primaryColor: null, backgroundColor: null, customCss: null },
identityProviders: [
{ slug: 'github', providerType: 'social', providerId: 'github', displayName: 'GitHub', icon: 'github' },
],
links: { termsUri: null, privacyUri: null, supportEmail: null },
copy: { productName: 'Realmroot', headline: 'Sign in', description: 'Secure access' },
auth: {
basePath: '/api/auth',
signInEmailPath: '/api/auth/sign-in/email',
signInUsernamePath: '/api/auth/sign-in/username',
signUpEmailPath: '/api/auth/sign-up/email',
signOutPath: '/api/auth/sign-out',
sendVerificationEmailPath: '/api/auth/send-verification-email',
verifyEmailPath: '/api/auth/verify-email',
emailOtpPath: '/api/auth/email-otp/send-verification-otp',
emailOtpSignInPath: '/api/auth/sign-in/email-otp',
emailOtpVerificationPath: '/api/auth/email-otp/verify-email',
emailOtpPasswordResetRequestPath: '/api/auth/email-otp/request-password-reset',
emailOtpPasswordResetPath: '/api/auth/email-otp/reset-password',
},
oidc: {
issuer: 'https://auth.example.com',
discoveryUrl: '/.well-known/openid-configuration',
authorizationEndpoint: '/oauth2/authorize',
tokenEndpoint: '/oauth2/token',
jwksUri: '/jwks',
userInfoEndpoint: '/userinfo',
endSessionEndpoint: '/logout',
},
security: { mfaRequired: false, sessionExpiresInSeconds: 3600, passkeysEnabled: true },
navigation: { externalLinks: [] },
accountCenter: {
profileEditingEnabled: true,
displayNameEditable: true,
usernameEditable: true,
avatarEditable: true,
emailChangeEnabled: true,
passwordChangeEnabled: true,
connectedAccountsEnabled: true,
sessionsViewEnabled: true,
dangerZoneEnabled: false,
},
captcha: { enabled: false, provider: 'turnstile', siteKey: '' },
}
}
type Handlers = Parameters<typeof setupServer>
export function accountHandlers(store: AccountStore, config: ReturnType<typeof configz> = configz()): Handlers {
return [
http.get(`${base}/api/configz`, () => HttpResponse.json(config)),
http.get(`${base}/api/account/profile`, () => HttpResponse.json({ user: store.profile })),
http.get(`${base}/api/account/developer-console-access`, () => HttpResponse.json(store.access)),
http.get(`${base}/api/account/organization-context`, () =>
HttpResponse.json({ activeOrganizationId: store.activeOrganizationId }),
),
http.get(`${base}/api/auth/organization/list`, () => HttpResponse.json([])),
http.get(`${base}/api/auth/organization/list-user-invitations`, () => HttpResponse.json([])),
http.patch(`${base}/api/account/profile`, async ({ request }) => {
const body = (await request.json()) as Record<string, unknown>
Object.assign(store.profile, body)
return HttpResponse.json({ user: store.profile })
}),
http.get(`${base}/api/account/security`, () => HttpResponse.json({ security: store.security })),
http.get(`${base}/api/account/security/passkeys`, () => HttpResponse.json({ passkeys: store.passkeys })),
http.get(`${base}/api/account/sessions`, () =>
HttpResponse.json({
items: store.sessions,
pagination: {
page: Math.floor(0 / 50) + 1,
pageSize: 50,
totalItems: store.sessions.length,
totalPages: Math.ceil(store.sessions.length / 50),
},
}),
),
http.get(`${base}/api/account/linked-accounts`, () =>
HttpResponse.json({
items: store.linkedAccounts,
pagination: {
page: Math.floor(0 / 50) + 1,
pageSize: 50,
totalItems: store.linkedAccounts.length,
totalPages: Math.ceil(store.linkedAccounts.length / 50),
},
}),
),
http.get(`${base}/api/account/provider-connectors`, () =>
HttpResponse.json({
items: [],
pagination: { page: Math.floor(0 / 50) + 1, pageSize: 50, totalItems: 0, totalPages: Math.ceil(0 / 50) },
}),
),
http.get(`${base}/api/account/provider-connections`, () =>
HttpResponse.json({
items: [],
pagination: { page: Math.floor(0 / 50) + 1, pageSize: 50, totalItems: 0, totalPages: Math.ceil(0 / 50) },
}),
),
http.get(`${base}/api/account/application-authorizations`, () =>
HttpResponse.json({
items: store.applications.map((authorization) => ({
...authorization,
applicationId: `app-${authorization.id}`,
application: {
id: `app-${authorization.id}`,
name: authorization.applicationName,
slug: authorization.applicationName.toLowerCase().replaceAll(' ', '-'),
},
user: { id: store.profile.id, displayName: store.profile.displayName, email: store.profile.email },
resourceServerId: null,
revokedAt: null,
status: 'active',
})),
pagination: {
page: 1,
pageSize: 50,
totalItems: store.applications.length,
totalPages: store.applications.length === 0 ? 0 : 1,
},
}),
),
http.get(`${base}/api/account/agents`, () =>
HttpResponse.json({
items: store.agentIdentities,
pagination: {
page: Math.floor(0 / 50) + 1,
pageSize: 50,
totalItems: store.agentIdentities.length,
totalPages: Math.ceil(store.agentIdentities.length / 50),
},
}),
),
http.get(`${base}/api/account/api-resources`, () =>
HttpResponse.json({
items: [],
pagination: { page: Math.floor(0 / 50) + 1, pageSize: 50, totalItems: 0, totalPages: Math.ceil(0 / 50) },
}),
),
http.get(`${base}/api/account/account-connections`, () =>
HttpResponse.json({
items: [],
pagination: { page: Math.floor(0 / 50) + 1, pageSize: 50, totalItems: 0, totalPages: Math.ceil(0 / 50) },
}),
),
] as unknown as Handlers
}
export function createAccountServer(store: AccountStore, config?: ReturnType<typeof configz>) {
return setupServer(...(accountHandlers(store, config) as unknown as Parameters<typeof setupServer>))
}
export function renderWithClient(
ui: ReactNode,
layout: {
organizationId?: string
pathname?: string
section?: Parameters<typeof AccountCenterLayout>[0]['section']
} = {},
) {
const queryClient = new QueryClient({ defaultOptions: { queries: { retry: false } } })
return {
queryClient,
...render(
<QueryClientProvider client={queryClient}>
<AccountCenterLayout
organizationId={layout.organizationId}
pathname={layout.pathname}
section={layout.section ?? 'overview'}
>
{ui}
</AccountCenterLayout>
</QueryClientProvider>,
),
}
}
export const json = HttpResponse.json
export { base, HttpResponse, http }