Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions e2e/account-email.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { expect, test } from './playwright-utils.ts'

test('verified user sees the account email inbox heading and address or empty list', async ({
page,
seedE2eUser,
login,
}) => {
const runId = Date.now()
const user = await seedE2eUser({
email: `account-email-${runId}@example.com`,
username: `account-email-${runId}`,
password: 'account-email-password',
})
await login({
email: user.email,
password: user.password,
mode: 'login',
})

await page.goto('/account/email')
await expect(
page.getByRole('heading', { name: 'Email inbox', exact: true }),
).toBeVisible()

const inboxAddress = page.locator('text=/Inbox address:/i')
const emptyList = page.getByText('No messages in your inbox yet.')
await expect(inboxAddress.or(emptyList).first()).toBeVisible()
})
101 changes: 101 additions & 0 deletions e2e/account-jobs.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import { expect, test } from './playwright-utils.ts'
import { quoteSqlString } from '@kody-internal/shared/sql-literals.ts'
import { createStableUserIdFromEmail } from '../packages/worker/src/user-id.ts'
import { executeE2eD1Command } from './d1-utils.ts'

test('account jobs page shows heading after login', async ({
page,
seedE2eUser,
login,
}) => {
const runId = Date.now()
const user = await seedE2eUser({
email: `account-jobs-${runId}@example.com`,
username: `account-jobs-${runId}`,
password: 'account-jobs-password',
})
await login({
email: user.email,
password: user.password,
mode: 'login',
})

await page.goto('/account/jobs')
await expect(
page.getByRole('heading', { name: 'Jobs', exact: true }),
).toBeVisible()
await expect(
page.getByRole('heading', { name: 'Select a job', exact: true }),
).toBeVisible()
})

test('account jobs detail URL persists across reload for a seeded job', async ({
page,
seedE2eUser,
login,
}) => {
const runId = Date.now()
const email = `account-jobs-detail-${runId}@example.com`
const user = await seedE2eUser({
email,
username: `account-jobs-detail-${runId}`,
password: 'account-jobs-password',
})
const userId = await createStableUserIdFromEmail(email)
const jobId = crypto.randomUUID()
const jobName = `Seeded job ${runId}`
const now = new Date().toISOString()
const nextRunAt = new Date(Date.now() + 60 * 60 * 1000).toISOString()
executeE2eD1Command(
`INSERT INTO jobs (
id, user_id, name, source_id, published_commit, storage_id,
params_json, schedule_json, timezone, enabled, kill_switch_enabled,
caller_context_json, created_at, updated_at, next_run_at,
run_count, success_count, error_count, run_history_json
) VALUES (
${quoteSqlString(jobId)},
${quoteSqlString(userId)},
${quoteSqlString(jobName)},
${quoteSqlString(`source-${jobId}`)},
NULL,
${quoteSqlString(`job:${jobId}`)},
NULL,
${quoteSqlString(JSON.stringify({ type: 'interval', every: '1h' }))},
'UTC',
1,
0,
'null',
${quoteSqlString(now)},
${quoteSqlString(now)},
${quoteSqlString(nextRunAt)},
0,
0,
0,
'[]'
)`,
)

await login({
email: user.email,
password: user.password,
mode: 'login',
})

await page.goto(`/account/jobs/${encodeURIComponent(jobId)}`)
await expect(
page.getByRole('heading', { name: 'Jobs', exact: true }),
).toBeVisible()
await expect(
page.getByRole('heading', { name: jobName, exact: true }),
).toBeVisible()

const detailUrl = page.url()
await page.reload()
await expect(page).toHaveURL(detailUrl)
await expect(
page.getByRole('heading', { name: jobName, exact: true }),
).toBeVisible()
await expect(
page.getByRole('definition').filter({ hasText: 'Runs every 1h' }),
).toBeVisible()
})
86 changes: 86 additions & 0 deletions e2e/account-memories.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import { expect, test } from './playwright-utils.ts'
import { quoteSqlString } from '@kody-internal/shared/sql-literals.ts'
import { createStableUserIdFromEmail } from '../packages/worker/src/user-id.ts'
import { executeE2eD1Command } from './d1-utils.ts'

test('account memories list shows empty state', async ({
page,
seedE2eUser,
login,
}) => {
const runId = Date.now()
const user = await seedE2eUser({
email: `account-memories-${runId}@example.com`,
username: `account-memories-${runId}`,
password: 'account-memories-password',
})
await login({
email: user.email,
password: user.password,
mode: 'login',
})

await page.goto('/account/memories')
await expect(
page.getByRole('heading', { name: 'Memories', exact: true }),
).toBeVisible()
await expect(
page.getByRole('heading', { name: 'Select a memory', exact: true }),
).toBeVisible()
})

test('account memories detail URL shows a seeded memory', async ({
page,
seedE2eUser,
login,
}) => {
const runId = Date.now()
const email = `account-memories-detail-${runId}@example.com`
const user = await seedE2eUser({
email,
username: `account-memories-detail-${runId}`,
password: 'account-memories-password',
})
const userId = await createStableUserIdFromEmail(email)
const memoryId = crypto.randomUUID()
const now = new Date().toISOString()
executeE2eD1Command(
`INSERT INTO mcp_memories (
id, user_id, category, status, subject, summary, details, tags_json,
source_uris_json, dedupe_key, created_at, updated_at, last_accessed_at, deleted_at
) VALUES (
${quoteSqlString(memoryId)},
${quoteSqlString(userId)},
'preferences',
'active',
${quoteSqlString(`Seeded memory ${runId}`)},
'Seeded for account memories e2e coverage.',
'Details for the seeded memory.',
${quoteSqlString(JSON.stringify(['e2e']))},
${quoteSqlString(JSON.stringify(['https://example.com/seeded-memory']))},
${quoteSqlString(`e2e:memory:${runId}`)},
${quoteSqlString(now)},
${quoteSqlString(now)},
NULL,
NULL
)`,
)

await login({
email: user.email,
password: user.password,
mode: 'login',
})

await page.goto(`/account/memories/${memoryId}`)
await expect(
page.getByRole('heading', { name: 'Memories', exact: true }),
).toBeVisible()
await expect(
page.getByRole('heading', {
name: `Seeded memory ${runId}`,
exact: true,
}),
).toBeVisible()
await expect(page.getByText('Details for the seeded memory.')).toBeVisible()
})
61 changes: 61 additions & 0 deletions e2e/account-values.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { expect, test } from './playwright-utils.ts'

test('account values use URL-backed list/detail selection', async ({
page,
seedE2eUser,
login,
}) => {
const runId = Date.now()
const user = await seedE2eUser({
email: `account-values-${runId}@example.com`,
username: `account-values-${runId}`,
password: 'account-values-password',
})
await login({
email: user.email,
password: user.password,
mode: 'login',
})

await page.goto('/account/values')
await expect(
page.getByRole('heading', { name: 'Values', exact: true }),
).toBeVisible()

await page.getByRole('button', { name: 'New value' }).click()
await expect(page).toHaveURL(/\/account\/values\/new$/)
await expect(
page.getByRole('heading', { name: 'New value', exact: true }),
).toBeVisible()

const valueName = `preferred-locale-${runId}`
const valueBody = `en-US-${runId}`
await page.getByLabel('Name', { exact: true }).fill(valueName)
await page.getByLabel('Description').fill('Preferred locale')
await page.getByLabel('Value', { exact: true }).fill(valueBody)
await page.getByRole('button', { name: 'Create value' }).click()

await expect(page).toHaveURL(
new RegExp(`/account/values/${encodeURIComponent(valueName)}$`),
)
await expect(
page.getByRole('heading', { name: valueName, exact: true }),
).toBeVisible()

const detailUrl = page.url()
await page.reload()
await expect(page).toHaveURL(detailUrl)
await expect(
page.getByRole('heading', { name: valueName, exact: true }),
).toBeVisible()
await expect(page.getByLabel('Name', { exact: true })).toHaveValue(valueName)
await expect(page.getByLabel('Name', { exact: true })).toHaveAttribute(
'readonly',
'',
)
await expect(page.getByLabel('Value', { exact: true })).toHaveValue(valueBody)

await page.getByLabel('Search', { exact: true }).fill(valueName)
await expect(page).toHaveURL(new RegExp(`q=${valueName}`))
await expect(page.getByRole('button', { name: valueName })).toBeVisible()
})
Loading
Loading