- Vitest unit tests: smart paste parsing (37), cost calculations (21),
oil translation (16), dialog system (12), with production data fixtures
- Cypress E2E tests: API CRUD (27), auth flow (8), recipe detail (10),
search (12), oil reference (4), favorites (6), inventory (6),
recipe management (10), diary (11), bug tracker (8), user management (13),
cost parity (6), data integrity (8), responsive (9), performance (6),
navigation (8), admin flow (5)
- Test coverage doc with prioritized gap analysis
- Found backend bug: POST /api/bug-reports/{id}/comment deletes the bug
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
240 lines
7.3 KiB
JavaScript
240 lines
7.3 KiB
JavaScript
describe('User Management Flow', () => {
|
|
const ADMIN_TOKEN = 'c86ae7afbe10fabe3c1d5e1a7fee74feaadfd5dc7be2ab62'
|
|
const authHeaders = { Authorization: `Bearer ${ADMIN_TOKEN}` }
|
|
const TEST_USERNAME = 'cypress_test_user_e2e'
|
|
const TEST_DISPLAY_NAME = 'Cypress E2E Test User'
|
|
let testUserId = null
|
|
|
|
describe('API: user lifecycle', () => {
|
|
// Cleanup any leftover test user first
|
|
before(() => {
|
|
cy.request({
|
|
url: '/api/users',
|
|
headers: authHeaders
|
|
}).then(res => {
|
|
const leftover = res.body.find(u => u.username === TEST_USERNAME)
|
|
if (leftover) {
|
|
cy.request({
|
|
method: 'DELETE',
|
|
url: `/api/users/${leftover.id || leftover._id}`,
|
|
headers: authHeaders,
|
|
failOnStatusCode: false
|
|
})
|
|
}
|
|
})
|
|
})
|
|
|
|
it('creates a new test user via API', () => {
|
|
cy.request({
|
|
method: 'POST',
|
|
url: '/api/users',
|
|
headers: authHeaders,
|
|
body: {
|
|
username: TEST_USERNAME,
|
|
display_name: TEST_DISPLAY_NAME,
|
|
role: 'viewer'
|
|
}
|
|
}).then(res => {
|
|
expect(res.status).to.be.oneOf([200, 201])
|
|
testUserId = res.body.id || res.body._id
|
|
// Should return a token for the new user
|
|
if (res.body.token) {
|
|
expect(res.body.token).to.be.a('string')
|
|
}
|
|
})
|
|
})
|
|
|
|
it('verifies the user appears in the user list', () => {
|
|
cy.request({
|
|
url: '/api/users',
|
|
headers: authHeaders
|
|
}).then(res => {
|
|
expect(res.body).to.be.an('array')
|
|
const found = res.body.find(u => u.username === TEST_USERNAME)
|
|
expect(found).to.exist
|
|
expect(found.display_name).to.eq(TEST_DISPLAY_NAME)
|
|
expect(found.role).to.eq('viewer')
|
|
testUserId = found.id || found._id
|
|
})
|
|
})
|
|
|
|
it('updates user role to editor', () => {
|
|
cy.request({
|
|
url: '/api/users',
|
|
headers: authHeaders
|
|
}).then(res => {
|
|
const found = res.body.find(u => u.username === TEST_USERNAME)
|
|
testUserId = found.id || found._id
|
|
cy.request({
|
|
method: 'PUT',
|
|
url: `/api/users/${testUserId}`,
|
|
headers: authHeaders,
|
|
body: { role: 'editor' }
|
|
}).then(res => {
|
|
expect(res.status).to.eq(200)
|
|
})
|
|
})
|
|
})
|
|
|
|
it('verifies role was updated', () => {
|
|
cy.request({
|
|
url: '/api/users',
|
|
headers: authHeaders
|
|
}).then(res => {
|
|
const found = res.body.find(u => u.username === TEST_USERNAME)
|
|
expect(found.role).to.eq('editor')
|
|
})
|
|
})
|
|
|
|
it('deletes the test user', () => {
|
|
cy.request({
|
|
url: '/api/users',
|
|
headers: authHeaders
|
|
}).then(res => {
|
|
const found = res.body.find(u => u.username === TEST_USERNAME)
|
|
if (found) {
|
|
testUserId = found.id || found._id
|
|
cy.request({
|
|
method: 'DELETE',
|
|
url: `/api/users/${testUserId}`,
|
|
headers: authHeaders
|
|
}).then(res => {
|
|
expect(res.status).to.eq(200)
|
|
})
|
|
}
|
|
})
|
|
})
|
|
|
|
it('verifies the user is deleted', () => {
|
|
cy.request({
|
|
url: '/api/users',
|
|
headers: authHeaders
|
|
}).then(res => {
|
|
const found = res.body.find(u => u.username === TEST_USERNAME)
|
|
expect(found).to.not.exist
|
|
})
|
|
})
|
|
})
|
|
|
|
describe('UI: users page renders', () => {
|
|
it('visits /users and verifies page structure', () => {
|
|
cy.visit('/users', {
|
|
onBeforeLoad(win) {
|
|
win.localStorage.setItem('oil_auth_token', ADMIN_TOKEN)
|
|
}
|
|
})
|
|
cy.get('.user-management', { timeout: 10000 }).should('exist')
|
|
cy.contains('用户管理').should('be.visible')
|
|
})
|
|
|
|
it('shows search input and role filter buttons', () => {
|
|
cy.visit('/users', {
|
|
onBeforeLoad(win) {
|
|
win.localStorage.setItem('oil_auth_token', ADMIN_TOKEN)
|
|
}
|
|
})
|
|
cy.get('.user-management', { timeout: 10000 }).should('exist')
|
|
// Search box
|
|
cy.get('.search-input').should('exist')
|
|
// Role filter buttons
|
|
cy.get('.filter-btn').should('have.length.gte', 1)
|
|
cy.get('.filter-btn').contains('管理员').should('exist')
|
|
cy.get('.filter-btn').contains('编辑').should('exist')
|
|
cy.get('.filter-btn').contains('查看者').should('exist')
|
|
})
|
|
|
|
it('displays user list with user cards', () => {
|
|
cy.visit('/users', {
|
|
onBeforeLoad(win) {
|
|
win.localStorage.setItem('oil_auth_token', ADMIN_TOKEN)
|
|
}
|
|
})
|
|
cy.get('.user-management', { timeout: 10000 }).should('exist')
|
|
cy.get('.user-card', { timeout: 5000 }).should('have.length.gte', 1)
|
|
// Each card shows name and role
|
|
cy.get('.user-card').first().within(() => {
|
|
cy.get('.user-name').should('not.be.empty')
|
|
cy.get('.user-role-badge').should('exist')
|
|
})
|
|
})
|
|
|
|
it('search filters users', () => {
|
|
cy.visit('/users', {
|
|
onBeforeLoad(win) {
|
|
win.localStorage.setItem('oil_auth_token', ADMIN_TOKEN)
|
|
}
|
|
})
|
|
cy.get('.user-management', { timeout: 10000 }).should('exist')
|
|
cy.get('.user-card').then($cards => {
|
|
const total = $cards.length
|
|
// Search for something specific
|
|
cy.get('.search-input').type('admin')
|
|
cy.wait(300)
|
|
cy.get('.user-card').should('have.length.lte', total)
|
|
})
|
|
})
|
|
|
|
it('role filter narrows user list', () => {
|
|
cy.visit('/users', {
|
|
onBeforeLoad(win) {
|
|
win.localStorage.setItem('oil_auth_token', ADMIN_TOKEN)
|
|
}
|
|
})
|
|
cy.get('.user-management', { timeout: 10000 }).should('exist')
|
|
cy.get('.user-card').then($cards => {
|
|
const total = $cards.length
|
|
// Click a role filter
|
|
cy.get('.filter-btn').contains('管理员').click()
|
|
cy.wait(300)
|
|
cy.get('.user-card').should('have.length.lte', total)
|
|
// Clicking again deactivates the filter
|
|
cy.get('.filter-btn').contains('管理员').click()
|
|
cy.wait(300)
|
|
cy.get('.user-card').should('have.length', total)
|
|
})
|
|
})
|
|
|
|
it('shows user count', () => {
|
|
cy.visit('/users', {
|
|
onBeforeLoad(win) {
|
|
win.localStorage.setItem('oil_auth_token', ADMIN_TOKEN)
|
|
}
|
|
})
|
|
cy.get('.user-management', { timeout: 10000 }).should('exist')
|
|
cy.get('.user-count').should('contain', '个用户')
|
|
})
|
|
|
|
it('has create user section', () => {
|
|
cy.visit('/users', {
|
|
onBeforeLoad(win) {
|
|
win.localStorage.setItem('oil_auth_token', ADMIN_TOKEN)
|
|
}
|
|
})
|
|
cy.get('.user-management', { timeout: 10000 }).should('exist')
|
|
cy.get('.create-section').should('exist')
|
|
cy.contains('创建新用户').should('be.visible')
|
|
})
|
|
})
|
|
|
|
// Safety cleanup
|
|
after(() => {
|
|
cy.request({
|
|
url: '/api/users',
|
|
headers: authHeaders,
|
|
failOnStatusCode: false
|
|
}).then(res => {
|
|
if (res.status === 200 && Array.isArray(res.body)) {
|
|
const testUsers = res.body.filter(u => u.username === TEST_USERNAME)
|
|
testUsers.forEach(user => {
|
|
cy.request({
|
|
method: 'DELETE',
|
|
url: `/api/users/${user.id || user._id}`,
|
|
headers: authHeaders,
|
|
failOnStatusCode: false
|
|
})
|
|
})
|
|
}
|
|
})
|
|
})
|
|
})
|