Add comprehensive test suite: 105 unit + 167 E2E tests

- 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>
This commit is contained in:
2026-04-06 19:47:47 +00:00
parent ee8ec23dc7
commit 2491479c2c
16 changed files with 3232 additions and 3 deletions

View File

@@ -0,0 +1,99 @@
describe('Bug Tracker Flow', () => {
const ADMIN_TOKEN = 'c86ae7afbe10fabe3c1d5e1a7fee74feaadfd5dc7be2ab62'
const authHeaders = { Authorization: `Bearer ${ADMIN_TOKEN}` }
const TEST_CONTENT = 'Cypress_E2E_Bug_测试缺陷_' + Date.now()
let testBugId = null
describe('API: bug lifecycle', () => {
it('submits a new bug via API', () => {
cy.request({
method: 'POST',
url: '/api/bug-report',
headers: authHeaders,
body: { content: TEST_CONTENT, priority: 2 }
}).then(res => {
expect(res.status).to.be.oneOf([200, 201])
})
})
it('verifies the bug appears in the list', () => {
cy.request({ url: '/api/bug-reports', headers: authHeaders }).then(res => {
expect(res.body).to.be.an('array')
const found = res.body.find(b => b.content && b.content.includes('Cypress_E2E_Bug'))
expect(found).to.exist
testBugId = found.id
})
})
it('updates bug status to testing', () => {
cy.request({ url: '/api/bug-reports', headers: authHeaders }).then(res => {
const found = res.body.find(b => b.content && b.content.includes('Cypress_E2E_Bug'))
testBugId = found.id
cy.request({
method: 'PUT',
url: `/api/bug-reports/${testBugId}`,
headers: authHeaders,
body: { status: 1, note: 'E2E test status change' }
}).then(r => expect(r.status).to.eq(200))
})
})
it('verifies status was updated', () => {
cy.request({ url: '/api/bug-reports', headers: authHeaders }).then(res => {
const found = res.body.find(b => b.content && b.content.includes('Cypress_E2E_Bug'))
expect(found.is_resolved).to.eq(1)
})
})
// NOTE: POST /api/bug-reports/{id}/comment has a backend bug — the decorator
// is stacked on delete_bug function, so POST to /comment actually deletes the bug.
// Skipping comment tests until backend is fixed.
it('bug has auto-generated creation comment', () => {
cy.request({ url: '/api/bug-reports', headers: authHeaders }).then(res => {
const found = res.body.find(b => b.content && b.content.includes('Cypress_E2E_Bug'))
expect(found).to.exist
expect(found.comments).to.be.an('array')
expect(found.comments.length).to.be.gte(1) // auto creation log
})
})
it('deletes the test bug', () => {
cy.request({ url: '/api/bug-reports', headers: authHeaders }).then(res => {
const found = res.body.find(b => b.content && b.content.includes('Cypress_E2E_Bug'))
if (found) {
cy.request({
method: 'DELETE',
url: `/api/bug-reports/${found.id}`,
headers: authHeaders
}).then(r => expect(r.status).to.eq(200))
}
})
})
it('verifies the bug is deleted', () => {
cy.request({ url: '/api/bug-reports', headers: authHeaders }).then(res => {
const found = res.body.find(b => b.content && b.content.includes('Cypress_E2E_Bug'))
expect(found).to.not.exist
})
})
})
describe('UI: bugs page', () => {
it('visits /bugs and page renders', () => {
cy.visit('/bugs', {
onBeforeLoad(win) { win.localStorage.setItem('oil_auth_token', ADMIN_TOKEN) }
})
cy.contains('Bug', { timeout: 10000 }).should('be.visible')
})
})
after(() => {
cy.request({ url: '/api/bug-reports', headers: authHeaders, failOnStatusCode: false }).then(res => {
if (res.status === 200 && Array.isArray(res.body)) {
res.body.filter(b => b.content && b.content.includes('Cypress_E2E_Bug')).forEach(bug => {
cy.request({ method: 'DELETE', url: `/api/bug-reports/${bug.id}`, headers: authHeaders, failOnStatusCode: false })
})
}
})
})
})