- Replace single-file 8441-line HTML with Vue 3 SPA - Pinia stores: auth, oils, recipes, diary, ui - Composables: useApi, useDialog, useSmartPaste, useOilTranslation - 6 shared components: RecipeCard, RecipeDetailOverlay, TagPicker, etc. - 9 page views: RecipeSearch, RecipeManager, Inventory, OilReference, etc. - 14 Cypress E2E test specs (113 tests), all passing - Multi-stage Dockerfile (Node build + Python runtime) - Demo video generation scripts (TTS + subtitles + screen recording) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
82 lines
3.0 KiB
JavaScript
82 lines
3.0 KiB
JavaScript
describe('Authentication Flow', () => {
|
|
it('shows login button when not authenticated', () => {
|
|
cy.visit('/')
|
|
cy.contains('登录').should('be.visible')
|
|
})
|
|
|
|
it('opens login modal when clicking login', () => {
|
|
cy.visit('/')
|
|
cy.contains('登录').click()
|
|
cy.get('[class*="overlay"], [class*="modal"], [class*="login"]').should('be.visible')
|
|
})
|
|
|
|
it('login modal has username and password fields', () => {
|
|
cy.visit('/')
|
|
cy.contains('登录').click()
|
|
cy.get('input[placeholder*="用户名"], input[type="text"]').should('exist')
|
|
cy.get('input[type="password"]').should('exist')
|
|
})
|
|
|
|
it('shows error for invalid login', () => {
|
|
cy.visit('/')
|
|
cy.contains('登录').click()
|
|
// Try submitting with invalid credentials
|
|
cy.get('input[placeholder*="用户名"], input[type="text"]').first().type('nonexistent_user_xyz')
|
|
cy.get('input[type="password"]').first().type('wrongpassword')
|
|
cy.contains('button', /登录|确定|提交/).click()
|
|
// Should show error (alert, toast, or inline message)
|
|
cy.wait(1000)
|
|
// The modal should still be visible (login failed)
|
|
cy.get('[class*="overlay"], [class*="modal"], [class*="login"]').should('exist')
|
|
})
|
|
|
|
it('authenticated user sees their name in header', () => {
|
|
const ADMIN_TOKEN = 'c86ae7afbe10fabe3c1d5e1a7fee74feaadfd5dc7be2ab62'
|
|
cy.visit('/', {
|
|
onBeforeLoad(win) {
|
|
win.localStorage.setItem('oil_auth_token', ADMIN_TOKEN)
|
|
}
|
|
})
|
|
cy.get('.app-header', { timeout: 8000 }).should('be.visible')
|
|
cy.contains('Hera').should('be.visible')
|
|
})
|
|
|
|
it('logout clears auth and shows login button', () => {
|
|
const ADMIN_TOKEN = 'c86ae7afbe10fabe3c1d5e1a7fee74feaadfd5dc7be2ab62'
|
|
cy.visit('/', {
|
|
onBeforeLoad(win) {
|
|
win.localStorage.setItem('oil_auth_token', ADMIN_TOKEN)
|
|
}
|
|
})
|
|
cy.contains('Hera', { timeout: 8000 }).should('be.visible')
|
|
// Click user name to open menu
|
|
cy.contains('Hera').click()
|
|
// Click logout
|
|
cy.contains(/退出|登出|logout/i).click()
|
|
// Should show login button again
|
|
cy.contains('登录', { timeout: 5000 }).should('be.visible')
|
|
})
|
|
|
|
it('token from URL param authenticates user', () => {
|
|
const ADMIN_TOKEN = 'c86ae7afbe10fabe3c1d5e1a7fee74feaadfd5dc7be2ab62'
|
|
cy.visit('/?token=' + ADMIN_TOKEN)
|
|
// Should authenticate and show user name
|
|
cy.contains('Hera', { timeout: 8000 }).should('be.visible')
|
|
// Token should be removed from URL
|
|
cy.url().should('not.include', 'token=')
|
|
})
|
|
|
|
it('protected tabs become accessible after login', () => {
|
|
const ADMIN_TOKEN = 'c86ae7afbe10fabe3c1d5e1a7fee74feaadfd5dc7be2ab62'
|
|
cy.visit('/', {
|
|
onBeforeLoad(win) {
|
|
win.localStorage.setItem('oil_auth_token', ADMIN_TOKEN)
|
|
}
|
|
})
|
|
cy.get('.nav-tab', { timeout: 10000 }).should('have.length.gte', 6)
|
|
cy.get('.nav-tab').contains('管理配方').click()
|
|
// Should navigate to manage page, not show login modal
|
|
cy.url().should('include', '/manage')
|
|
})
|
|
})
|