Files
euchre_camp/src/__tests__/Navigation.test.tsx
T
david 861e14503b
Release / release (push) Failing after 9s
Build CI Images / build-ci-base (push) Failing after 20s
feat: SDLC database separation for CI/testing (#35)
## Summary
- Fix `isProductionDatabase()` to allow CI database (`euchre_camp_ci`)
- Add database schema reset before CI test runs
- Create `global.teardown.ts` for cleanup (CI: full reset, dev/prod: selective cleanup)
- Add `acceptance-tests` job to PR workflow with CI database
- Create `sync-prod-to-dev.js` script for one-way prod→dev sync
- Add `just` recipes: `sync-dev`, `test-prod`, `reset-ci-db`
- Store credentials in `.credentials` (gitignored) with unique CI user

## Testing
Verified against CI database:
- Schema reset works
- Migrations apply correctly
- Test users created successfully
- 219 tests pass (slow but working)

## Next Steps
- Set `CI_DATABASE_URL` as Gitea repository variable

Reviewed-on: #35
Co-authored-by: David Gwilliam <dhgwilliam@gmail.com>
Co-committed-by: David Gwilliam <dhgwilliam@gmail.com>
2026-05-11 06:40:05 +00:00

209 lines
5.6 KiB
TypeScript

/**
* Epic 1: Authentication & User Management
* Component Test: Navigation
*
* Tests the navigation component for proper display based on session state
*/
import { describe, it, expect, mock, beforeEach, afterEach } from 'bun:test'
import { render, screen, waitFor } from '@testing-library/react'
import Navigation from '@/components/Navigation'
import { RoleSwitcherProvider } from '@/components/RoleSwitcher'
// Mock next/link
mock.module('next/link', () => ({
default: ({ children, href }: { children: React.ReactNode; href: string }) => (
<a href={href}>{children}</a>
),
}))
// Mock the SessionProvider
mock.module('@/components/SessionProvider', () => ({
useSession: mock(() => ({ session: null, loading: false, refreshSession: mock(() => {}) })),
}))
// Mock the auth-client
mock.module('@/lib/auth-client', () => ({
authClient: {
signOut: mock(() => {}),
},
}))
// Mock fetch for role API call
global.fetch = mock(async () => new Response()) as any
function renderNavigation() {
return render(
<RoleSwitcherProvider>
<Navigation />
</RoleSwitcherProvider>
)
}
import { useSession as useSessionOriginal } from '@/components/SessionProvider'
const useSession = useSessionOriginal as any
describe('Epic 1: Navigation Component', () => {
beforeEach(() => {
// Don't clear all mocks as it might affect bun-setup.ts
// Set up default fetch mock
(global.fetch as any).mockImplementation?.(async (url: any) => {
if (url?.toString().includes('/api/users/')) {
return new Response(JSON.stringify({ role: 'player' }), {
status: 200,
headers: { 'Content-Type': 'application/json' },
})
}
return new Response(JSON.stringify({}), { status: 200 })
})
})
afterEach(() => {
// Don't clear mocks - let them persist for other test files
// Navigation relies on module mocks set up at file level
})
it('renders the logo and basic links when not logged in', () => {
(useSession).mockReturnValue({
session: null,
loading: false,
refreshSession: mock(() => {}),
})
renderNavigation()
expect(screen.getByText('EuchreCamp')).toBeInTheDocument()
expect(screen.getByText('Rankings')).toBeInTheDocument()
expect(screen.getByText('Sign in')).toBeInTheDocument()
expect(screen.getByText('Sign up')).toBeInTheDocument()
})
it('shows user menu when logged in', async () => {
(useSession).mockReturnValue({
session: {
user: {
id: 'user-123',
email: 'test@example.com',
name: 'Test User',
role: 'player'
},
session: { token: 'abc123' },
},
loading: false,
refreshSession: mock(() => {}),
})
renderNavigation()
await waitFor(() => {
expect(screen.getByText('Test User')).toBeInTheDocument()
})
expect(screen.getByText('Sign out')).toBeInTheDocument()
expect(screen.getByText('Tournaments')).toBeInTheDocument()
})
it('shows Tournaments link when logged in', async () => {
(useSession).mockReturnValue({
session: {
user: {
id: 'user-123',
email: 'test@example.com',
name: 'Test User',
role: 'player'
},
session: { token: 'abc123' },
},
loading: false,
refreshSession: mock(() => {}),
})
renderNavigation()
await waitFor(() => {
expect(screen.getByText('Tournaments')).toBeInTheDocument()
})
})
it('shows admin link for club_admin role', async () => {
(useSession).mockReturnValue({
session: {
user: {
id: 'admin-123',
email: 'admin@example.com',
name: 'Admin User',
role: 'club_admin',
},
session: { token: 'abc123' },
},
loading: false,
refreshSession: mock(() => {}),
});
// Mock fetch to return club_admin role
(global.fetch as any).mockImplementation(async (url: any) => {
if (url?.toString().includes('/api/users/admin-123/role')) {
return new Response(JSON.stringify({ role: 'club_admin' }), {
status: 200,
headers: { 'Content-Type': 'application/json' },
})
}
return new Response(JSON.stringify({}), { status: 200 })
})
renderNavigation()
await waitFor(() => {
expect(screen.getByText('Admin')).toBeInTheDocument()
}, { timeout: 3000 })
expect(screen.getByText('Tournaments')).toBeInTheDocument()
})
it('hides admin link for non-admin users', async () => {
(useSession).mockReturnValue({
session: {
user: {
id: 'player-123',
email: 'player@example.com',
name: 'Player User',
role: 'player',
},
session: { token: 'abc123' },
},
loading: false,
refreshSession: mock(() => {}),
});
// Mock fetch to return player role (already set in beforeEach, but explicit here)
(global.fetch as any).mockImplementation(async (url: any) => {
if (url?.toString().includes('/api/users/')) {
return {
json: () => Promise.resolve({ role: 'player' }),
} as Response
}
return {
json: () => Promise.resolve({}),
} as Response
})
renderNavigation()
await waitFor(() => {
expect(screen.getByText('Tournaments')).toBeInTheDocument()
})
expect(screen.queryByText('Admin')).not.toBeInTheDocument()
})
it('shows loading state', () => {
(useSession).mockReturnValue({
session: null,
loading: true,
refreshSession: mock(() => {}),
})
renderNavigation()
expect(screen.getByText('Loading...')).toBeInTheDocument()
})
})