Some checks failed
PR Preview / teardown-preview (pull_request) Has been skipped
Test / unit-test (push) Successful in 5s
Test / build-check (push) Successful in 4s
PR Preview / test (pull_request) Successful in 5s
PR Preview / deploy-preview (pull_request) Successful in 12s
Test / e2e-test (push) Failing after 2m14s
根本原因: 所有测试硬编码了只在生产环境有效的admin token, CI创建新数据库时token不同导致全部认证失败。 修复: - CI: 设置已知ADMIN_TOKEN环境变量传给后端和Cypress - cypress/support/e2e.js: 新增cy.getAdminToken()动态获取token - 24个spec文件: 硬编码token改为cy.getAdminToken() - UI选择器: 适配管理页面从tab移到UserMenu、编辑器DOM变化 - API: create_recipe→share_recipe、ingredients格式、权限变化 - 超时: 300s→420s适应32个spec Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
86 lines
2.5 KiB
JavaScript
86 lines
2.5 KiB
JavaScript
describe('Recipe Cost Parity Test', () => {
|
|
// Verify recipe cost formula: cost = sum(bottle_price / drop_count * drops)
|
|
|
|
let oilsMap = {}
|
|
let testRecipes = []
|
|
|
|
before(() => {
|
|
cy.request('/api/oils').then(res => {
|
|
res.body.forEach(oil => {
|
|
oilsMap[oil.name] = {
|
|
bottle_price: oil.bottle_price,
|
|
drop_count: oil.drop_count,
|
|
ppd: oil.drop_count ? oil.bottle_price / oil.drop_count : 0,
|
|
retail_price: oil.retail_price
|
|
}
|
|
})
|
|
})
|
|
cy.request('/api/recipes').then(res => {
|
|
testRecipes = res.body.slice(0, 20)
|
|
})
|
|
})
|
|
|
|
it('oil data has correct structure (100+ oils)', () => {
|
|
expect(Object.keys(oilsMap).length).to.be.gte(100)
|
|
})
|
|
|
|
it('price-per-drop matches formula for available oils', () => {
|
|
const checks = ['薰衣草', '乳香', '茶树', '柠檬', '椒样薄荷']
|
|
checks.forEach(name => {
|
|
const oil = oilsMap[name]
|
|
if (oil) {
|
|
const expected = oil.bottle_price / oil.drop_count
|
|
expect(oil.ppd).to.be.closeTo(expected, 0.0001)
|
|
}
|
|
})
|
|
})
|
|
|
|
it('calculates cost for each of first 20 recipes', () => {
|
|
testRecipes.forEach(recipe => {
|
|
let cost = 0
|
|
recipe.ingredients.forEach(ing => {
|
|
const oil = oilsMap[ing.oil_name]
|
|
if (oil) cost += oil.ppd * ing.drops
|
|
})
|
|
expect(cost).to.be.gte(0)
|
|
})
|
|
})
|
|
|
|
it('retail price >= wholesale for oils that have it', () => {
|
|
Object.entries(oilsMap).forEach(([name, oil]) => {
|
|
if (oil.retail_price && oil.retail_price > 0) {
|
|
expect(oil.retail_price).to.be.gte(oil.bottle_price)
|
|
}
|
|
})
|
|
})
|
|
|
|
it('no recipe has all-zero cost', () => {
|
|
if (testRecipes.length === 0) return
|
|
let zeroCostCount = 0
|
|
testRecipes.forEach(recipe => {
|
|
let cost = 0
|
|
recipe.ingredients.forEach(ing => {
|
|
const oil = oilsMap[ing.oil_name]
|
|
if (oil) cost += oil.ppd * ing.drops
|
|
})
|
|
if (cost === 0) zeroCostCount++
|
|
})
|
|
expect(zeroCostCount).to.be.lt(testRecipes.length)
|
|
})
|
|
|
|
it('cost formula is consistent: two calculation methods agree', () => {
|
|
testRecipes.forEach(recipe => {
|
|
const costs = recipe.ingredients.map(ing => {
|
|
const oil = oilsMap[ing.oil_name]
|
|
return oil ? oil.ppd * ing.drops : 0
|
|
})
|
|
const fromMap = costs.reduce((a, b) => a + b, 0)
|
|
const fromReduce = recipe.ingredients.reduce((s, ing) => {
|
|
const oil = oilsMap[ing.oil_name]
|
|
return s + (oil ? oil.ppd * ing.drops : 0)
|
|
}, 0)
|
|
expect(fromMap).to.be.closeTo(fromReduce, 0.001)
|
|
})
|
|
})
|
|
})
|