- 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>
89 lines
2.6 KiB
JavaScript
89 lines
2.6 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 (137+ oils)', () => {
|
|
expect(Object.keys(oilsMap).length).to.be.gte(100)
|
|
const lav = oilsMap['薰衣草']
|
|
expect(lav).to.exist
|
|
expect(lav.bottle_price).to.be.gt(0)
|
|
expect(lav.drop_count).to.be.gt(0)
|
|
})
|
|
|
|
it('price-per-drop matches formula for common 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', () => {
|
|
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)
|
|
})
|
|
})
|
|
})
|