Finish the database key-in-object refactoring.

This commit is contained in:
2026-01-27 02:11:09 +00:00
parent 0f29544bdb
commit 9bdca1f43a
16 changed files with 415 additions and 412 deletions
+23 -23
View File
@@ -138,9 +138,9 @@ test.describe('Passkey Authentication E2E', () => {
const validation = await validateSession(page, baseUrl, sessionToken)
expect(validation.valid).toBe(true)
expect(validation.user_uuid).toBe(userUuid)
expect(validation.ctx.user.uuid).toBe(userUuid)
console.log(`✓ Session validated for user: ${validation.user_uuid}`)
console.log(`✓ Session validated for user: ${validation.ctx.user.uuid}`)
})
test('should retrieve user info', async ({ page }) => {
@@ -148,8 +148,8 @@ test.describe('Passkey Authentication E2E', () => {
const userInfo = await getUserInfo(page, baseUrl, sessionToken)
expect(userInfo.user.user_uuid).toBe(userUuid)
expect(userInfo.user.user_name).toBe('Admin User')
expect(userInfo.ctx.user.uuid).toBe(userUuid)
expect(userInfo.ctx.user.display_name).toBe('Admin User')
expect(userInfo.credentials).toBeDefined()
expect(userInfo.credentials.length).toBeGreaterThanOrEqual(1)
@@ -169,7 +169,7 @@ test.describe('Passkey Authentication E2E', () => {
await page.screenshot({ path: 'test-results/profile-view.png' })
console.log('✓ Screenshot saved: test-results/profile-view.png')
console.log(`✓ User info retrieved: ${userInfo.user.user_name}`)
console.log(`✓ User info retrieved: ${userInfo.ctx.user.display_name}`)
console.log(`✓ Credentials count: ${userInfo.credentials.length}`)
})
@@ -219,7 +219,7 @@ test.describe('Passkey Authentication E2E', () => {
const validation = await validateSession(page, baseUrl, sessionToken)
expect(validation.valid).toBe(true)
expect(validation.user_uuid).toBe(userUuid)
expect(validation.ctx.user.uuid).toBe(userUuid)
console.log(`✓ New session validated`)
})
@@ -291,8 +291,8 @@ test.describe('Device Addition Dialog', () => {
// Wait for the profile view to load
await page.waitForSelector('[data-view="profile"]', { timeout: 5000 })
// Click the "Add Another Device" button
const addDeviceButton = page.getByRole('button', { name: 'Add Another Device' })
// Click the "Another Device" button
const addDeviceButton = page.getByRole('button', { name: 'Another Device' })
await expect(addDeviceButton).toBeVisible()
await addDeviceButton.click()
@@ -301,7 +301,7 @@ test.describe('Device Addition Dialog', () => {
await expect(dialog).toBeVisible({ timeout: 5000 })
// Verify dialog contains expected elements
await expect(dialog.locator('h2')).toContainText('Device Registration Link')
await expect(dialog.locator('h2')).toContainText('Add Another Device')
// Wait for QR code to be generated (canvas should have content)
const qrCanvas = dialog.locator('.qr-code')
@@ -318,16 +318,16 @@ test.describe('Device Addition Dialog', () => {
expect(linkHref).toContain('http://localhost:4404/auth/')
console.log(`✓ Device link displayed: ${linkText} (href: ${linkHref})`)
// Verify expiration warning is shown
await expect(dialog.locator('.reg-help')).toContainText('Expires')
// Verify help text is shown
await expect(dialog.locator('.reg-help')).toContainText('Scan this QR code')
// Take screenshot of the dialog
await dialog.screenshot({ path: 'test-results/device-addition-dialog.png' })
console.log(`✓ Screenshot saved: test-results/device-addition-dialog.png`)
// Verify Copy Link button exists
const copyButton = dialog.getByRole('button', { name: 'Copy Link' })
await expect(copyButton).toBeVisible()
// Verify the QR link element is clickable (copy functionality is built into clicking it)
const qrLink = dialog.locator('a.qr-link')
await expect(qrLink).toBeVisible()
// Close the dialog (use the text button, not the icon button)
const closeButton = dialog.locator('button.btn-secondary', { hasText: 'Close' })
@@ -357,12 +357,12 @@ test.describe('Device Addition Dialog', () => {
await page.waitForSelector('[data-view="profile"]', { timeout: 5000 })
// Open the dialog
await page.getByRole('button', { name: 'Add Another Device' }).click()
await page.getByRole('button', { name: 'Another Device' }).click()
const dialog = page.locator('.device-dialog')
await expect(dialog).toBeVisible({ timeout: 5000 })
// Extract the reset token from the displayed URL
const linkText = dialog.locator('.qr-link p')
const linkText = dialog.locator('.qr-link .link-text')
const linkContent = await linkText.textContent()
// URL format: localhost/auth/word1.word2.word3.word4.word5
@@ -405,7 +405,7 @@ test.describe('Device Addition Dialog', () => {
})
})
test.describe('ProfileView - Add New Passkey', () => {
test.describe('ProfileView - Register New', () => {
const baseUrl = process.env.BASE_URL || 'http://localhost:4404'
test('should show credentials list in profile', async ({ page }) => {
@@ -427,7 +427,7 @@ test.describe('ProfileView - Add New Passkey', () => {
console.log(`✓ Profile shows ${credentialItems} credential(s) in list`)
})
test('should add a new passkey using Add New Passkey button', async ({ page }) => {
test('should add a new passkey using Register New button', async ({ page }) => {
const sessionToken = getSavedSessionToken()
test.skip(!sessionToken, 'Requires saved session token')
@@ -444,8 +444,8 @@ test.describe('ProfileView - Add New Passkey', () => {
const initialCredentialCount = await page.locator('.credential-item').count()
console.log(`Initial credential count: ${initialCredentialCount}`)
// Click "Add New Passkey" button
const addPasskeyBtn = page.locator('button:has-text("Add New Passkey")')
// Click "Register New" button
const addPasskeyBtn = page.locator('button:has-text("Register New")')
await expect(addPasskeyBtn).toBeVisible()
await addPasskeyBtn.click()
@@ -490,7 +490,7 @@ test.describe('ProfileView - Add New Passkey', () => {
// Try to add a passkey - with excludeCredentials the authenticator should
// prevent re-registration of the same credential
const addPasskeyBtn = page.locator('button:has-text("Add New Passkey")')
const addPasskeyBtn = page.locator('button:has-text("Register New")')
await expect(addPasskeyBtn).toBeVisible()
await addPasskeyBtn.click()
@@ -541,8 +541,8 @@ test.describe('ProfileView - Multi-Authenticator', () => {
await page.waitForSelector('.credential-list', { timeout: 10000 })
const initialCredentialCount = await page.locator('.credential-item').count()
// Click "Add New Passkey" button
const addPasskeyBtn = page.locator('button:has-text("Add New Passkey")')
// Click "Register New" button
const addPasskeyBtn = page.locator('button:has-text("Register New")')
await expect(addPasskeyBtn).toBeVisible()
await addPasskeyBtn.click()
+1 -1
View File
@@ -268,7 +268,7 @@ test.describe('API Mode - 401 Login Flow', () => {
// Wait for API call to complete and verify result
const result = await apiCallPromise
expect(result.status).toBe(200)
expect(result.data.user).toBeDefined()
expect(result.data.ctx).toBeDefined()
console.log('✓ API call succeeded after authentication')
// Save the session for other tests
+36 -2
View File
@@ -23,6 +23,40 @@ export interface AuthenticationResult {
session_token: string
}
export interface SessionContext {
user: { uuid: string; display_name: string }
org: { uuid: string; display_name: string }
role: { uuid: string; display_name: string }
permissions: string[]
}
export interface UserInfo {
ctx: SessionContext
created_at: string
last_seen: string
visits: number
credentials: Array<{
credential_uuid: string
aaguid: string
created_at: string
last_used: string | null
last_verified: string | null
sign_count: number
is_current_session: boolean
}>
aaguid_info: Record<string, { name: string; icon_light?: string; icon_dark?: string }>
sessions: Array<{
id: string
credential_uuid: string
host: string
ip: string
user_agent: string
last_renewed: string
is_current: boolean
is_current_host: boolean
}>
}
/**
* Get the bootstrap reset token from the test state file.
*/
@@ -376,7 +410,7 @@ export async function validateSession(
page: Page,
baseUrl: string,
sessionToken: string
): Promise<{ valid: boolean; user_uuid: string; renewed: boolean }> {
): Promise<{ valid: boolean; ctx: SessionContext; renewed: boolean }> {
const cookieName = getSessionCookieName()
const response = await page.request.post(`${baseUrl}/auth/api/validate`, {
headers: {
@@ -393,7 +427,7 @@ export async function getUserInfo(
page: Page,
baseUrl: string,
sessionToken: string
): Promise<any> {
): Promise<UserInfo> {
const cookieName = getSessionCookieName()
const response = await page.request.post(`${baseUrl}/auth/api/user-info`, {
headers: {
+6 -4
View File
@@ -42,21 +42,23 @@ export default async function globalSetup() {
const serverArgs = COLLECT_COVERAGE
? [
'run', 'coverage', 'run', '--parallel-mode',
'-m', 'paskia.fastapi', 'serve', 'localhost:4404',
'-m', 'paskia.fastapi', 'localhost:4404',
'--rp-id', 'localhost'
]
: [
'run', 'paskia', 'serve', 'localhost:4404',
'run', 'paskia', 'localhost:4404',
'--rp-id', 'localhost'
]
// Use a temporary jsonl file for test database
const testDbFile = join(testDataDir, 'test-db.jsonl')
// Start the server using Node's spawn
// Use in-memory SQLite for faster tests
const serverProcess = spawn('uv', serverArgs, {
cwd: projectRoot,
env: {
...process.env,
PASKIA_DB: 'sqlite+aiosqlite:///:memory:',
PASKIA_DB: testDbFile,
COVERAGE_FILE: join(projectRoot, '.coverage'),
},
stdio: ['ignore', 'pipe', 'pipe'],
+5 -12
View File
@@ -59,18 +59,11 @@ export default async function globalTeardown() {
rmSync(stateFile, { force: true })
}
// Optionally clean up test database (keep it for debugging by default)
if (process.env.CLEANUP_TEST_DB === 'true') {
const dbPath = join(testDataDir, 'test.sqlite')
if (existsSync(dbPath)) {
console.log(' Removing test database...')
rmSync(dbPath)
}
// Remove wal/shm files too
for (const ext of ['-wal', '-shm']) {
const file = dbPath + ext
if (existsSync(file)) rmSync(file)
}
// Clean up test database
const testDbFile = join(testDataDir, 'test-db.jsonl')
if (existsSync(testDbFile)) {
console.log(' Removing test database...')
rmSync(testDbFile)
}
// Generate Python coverage report if coverage was collected