Some checks failed
- Backend: FastAPI + SQLite (WAL mode), 22 tables, ~40 API endpoints - Frontend: Vue 3 + Vite + Pinia + Vue Router, 8 views, 3 stores - Database: migrate from JSON file to SQLite with proper schema - Dockerfile: multi-stage build (node + python) - Deploy: K8s manifests (namespace, deployment, service, ingress, pvc, backup) - CI/CD: Gitea Actions (test, deploy, PR preview at pr-$id.planner.oci.euphon.net) - Tests: 20 Cypress E2E test files, 196 test cases, ~85% coverage - Doc: test-coverage.md with full feature coverage report Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
129 lines
4.5 KiB
JavaScript
129 lines
4.5 KiB
JavaScript
describe('Sleep Buddy (睡眠打卡)', () => {
|
|
beforeEach(() => {
|
|
cy.visit('/sleep-buddy', {
|
|
onBeforeLoad(win) {
|
|
win.localStorage.setItem('sp_login_expires', String(Date.now() + 86400000))
|
|
}
|
|
})
|
|
})
|
|
|
|
it('shows login form when not logged in as buddy', () => {
|
|
cy.get('.buddy-login').should('be.visible')
|
|
cy.get('.buddy-login-logo').should('contain', '🌙')
|
|
cy.contains('睡眠打卡').should('be.visible')
|
|
cy.contains('和好友一起早睡').should('be.visible')
|
|
})
|
|
|
|
it('has username and password fields', () => {
|
|
cy.get('.buddy-login-card input').should('have.length.gte', 2)
|
|
cy.get('.buddy-login-card input[type="password"]').should('exist')
|
|
})
|
|
|
|
it('toggle between login and register mode', () => {
|
|
cy.get('.buddy-toggle-btn').should('contain', '没有账号?注册')
|
|
cy.get('.buddy-toggle-btn').click()
|
|
cy.get('.buddy-main-btn').should('contain', '注册')
|
|
cy.get('.buddy-login-card input').should('have.length', 3) // username, password, confirm
|
|
cy.get('.buddy-toggle-btn').should('contain', '已有账号?登录')
|
|
})
|
|
|
|
it('register mode shows confirm password', () => {
|
|
cy.get('.buddy-toggle-btn').click()
|
|
cy.get('.buddy-login-card input[type="password"]').should('have.length', 2)
|
|
})
|
|
|
|
it('shows error for mismatched passwords during register', () => {
|
|
cy.get('.buddy-toggle-btn').click()
|
|
cy.get('.buddy-login-card input').eq(0).type('testuser')
|
|
cy.get('.buddy-login-card input').eq(1).type('pass1')
|
|
cy.get('.buddy-login-card input').eq(2).type('pass2')
|
|
cy.get('.buddy-main-btn').click()
|
|
cy.get('.buddy-error').should('contain', '密码不一致')
|
|
})
|
|
|
|
it('register then login flow', () => {
|
|
const user = 'testuser_' + Date.now()
|
|
// Register
|
|
cy.get('.buddy-toggle-btn').click()
|
|
cy.get('.buddy-login-card input').eq(0).type(user)
|
|
cy.get('.buddy-login-card input').eq(1).type('testpass')
|
|
cy.get('.buddy-login-card input').eq(2).type('testpass')
|
|
cy.get('.buddy-main-btn').click()
|
|
// Should be logged in
|
|
cy.get('.buddy-main', { timeout: 5000 }).should('be.visible')
|
|
cy.contains(user).should('be.visible')
|
|
})
|
|
|
|
// Tests that require buddy login
|
|
describe('when logged in', () => {
|
|
const user = 'cy_test_' + Math.random().toString(36).slice(2, 8)
|
|
|
|
beforeEach(() => {
|
|
cy.visit('/sleep-buddy', {
|
|
onBeforeLoad(win) {
|
|
win.localStorage.setItem('sp_login_expires', String(Date.now() + 86400000))
|
|
win.localStorage.setItem('buddy_session', JSON.stringify({
|
|
username: user,
|
|
exp: Date.now() + 86400000
|
|
}))
|
|
}
|
|
})
|
|
// Register the user via API first
|
|
cy.window().then(async (win) => {
|
|
const buf = await win.crypto.subtle.digest('SHA-256', new TextEncoder().encode('testpass'))
|
|
const hash = Array.from(new Uint8Array(buf)).map(b => b.toString(16).padStart(2, '0')).join('')
|
|
try {
|
|
await fetch('/api/buddy-register', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ username: user, hash })
|
|
})
|
|
} catch {}
|
|
})
|
|
})
|
|
|
|
it('shows main buddy interface', () => {
|
|
cy.get('.buddy-main', { timeout: 5000 }).should('be.visible')
|
|
cy.get('.sleep-btn').should('contain', '我去睡觉啦')
|
|
})
|
|
|
|
it('shows target time card', () => {
|
|
cy.get('.target-card').should('be.visible')
|
|
cy.get('.target-time').should('not.be.empty')
|
|
})
|
|
|
|
it('shows record input', () => {
|
|
cy.get('.record-card').should('be.visible')
|
|
cy.get('.capture-row input').should('be.visible')
|
|
})
|
|
|
|
it('records sleep time', () => {
|
|
cy.get('.capture-row input').type('22:30')
|
|
cy.get('.capture-row button').click()
|
|
cy.get('.buddy-hint').should('contain', '已记录')
|
|
})
|
|
|
|
it('shows error for unrecognized input', () => {
|
|
cy.get('.capture-row input').type('乱七八糟')
|
|
cy.get('.capture-row button').click()
|
|
cy.get('.buddy-hint').should('contain', '无法识别')
|
|
})
|
|
|
|
it('go sleep button sends notification', () => {
|
|
cy.get('.sleep-btn').click()
|
|
cy.get('.buddy-hint').should('contain', '晚安')
|
|
})
|
|
|
|
it('user menu shows logout', () => {
|
|
cy.get('.user-chip').click()
|
|
cy.get('.user-menu button').should('contain', '退出登录')
|
|
})
|
|
|
|
it('logout returns to login form', () => {
|
|
cy.get('.user-chip').click()
|
|
cy.contains('退出登录').click()
|
|
cy.get('.buddy-login').should('be.visible')
|
|
})
|
|
})
|
|
})
|