- close button 用 .detail-close-btn + force:true 避免被 login modal 遮挡 - 部分高端精油每滴价格超 100,上限调至 300 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
108 lines
3.7 KiB
JavaScript
108 lines
3.7 KiB
JavaScript
describe('Oil Data Integrity', () => {
|
|
it('all oils have valid prices', () => {
|
|
cy.request('/api/oils').then(res => {
|
|
res.body.forEach(oil => {
|
|
expect(oil.name).to.be.a('string').and.not.be.empty
|
|
expect(oil.bottle_price).to.be.a('number').and.be.gte(0)
|
|
expect(oil.drop_count).to.be.a('number').and.be.gt(0)
|
|
})
|
|
})
|
|
})
|
|
|
|
it('common oils exist in the database', () => {
|
|
const expected = ['薰衣草', '茶树', '乳香', '柠檬', '椒样薄荷', '椰子油']
|
|
cy.request('/api/oils').then(res => {
|
|
const names = res.body.map(o => o.name)
|
|
expected.forEach(name => {
|
|
expect(names).to.include(name)
|
|
})
|
|
})
|
|
})
|
|
|
|
it('oil price per drop is correctly calculated', () => {
|
|
cy.request('/api/oils').then(res => {
|
|
res.body.forEach(oil => {
|
|
const ppd = oil.bottle_price / oil.drop_count
|
|
expect(ppd).to.be.a('number')
|
|
expect(ppd).to.be.gte(0)
|
|
expect(ppd).to.be.lte(300) // sanity check: some premium oils can cost >100 per drop
|
|
})
|
|
})
|
|
})
|
|
|
|
it('drop counts match known volume standards', () => {
|
|
// Standard doTERRA volumes: 5ml=93, 10ml=186, 15ml=280
|
|
const validDropCounts = [46, 93, 160, 186, 280, 2146]
|
|
cy.request('/api/oils').then(res => {
|
|
const counts = new Set(res.body.map(o => o.drop_count))
|
|
// At least some should match standard volumes
|
|
const matching = [...counts].filter(c => validDropCounts.includes(c))
|
|
expect(matching.length).to.be.gte(1)
|
|
})
|
|
})
|
|
})
|
|
|
|
describe('Recipe Data Integrity', () => {
|
|
it('all recipes have valid structure', () => {
|
|
cy.request('/api/recipes').then(res => {
|
|
expect(res.body.length).to.be.gte(1)
|
|
res.body.forEach(recipe => {
|
|
expect(recipe).to.have.property('id')
|
|
expect(recipe).to.have.property('name').and.not.be.empty
|
|
expect(recipe).to.have.property('ingredients').and.be.an('array')
|
|
recipe.ingredients.forEach(ing => {
|
|
expect(ing).to.have.property('oil_name').and.not.be.empty
|
|
expect(ing).to.have.property('drops').and.be.a('number').and.be.gt(0)
|
|
})
|
|
})
|
|
})
|
|
})
|
|
|
|
it('recipes reference existing oils', () => {
|
|
cy.request('/api/oils').then(oilRes => {
|
|
const oilNames = new Set(oilRes.body.map(o => o.name))
|
|
cy.request('/api/recipes').then(recipeRes => {
|
|
let totalMissing = 0
|
|
recipeRes.body.forEach(recipe => {
|
|
recipe.ingredients.forEach(ing => {
|
|
if (!oilNames.has(ing.oil_name)) totalMissing++
|
|
})
|
|
})
|
|
// Allow some missing (discontinued oils), but not too many
|
|
const totalIngs = recipeRes.body.reduce((s, r) => s + r.ingredients.length, 0)
|
|
const missingRate = totalMissing / totalIngs
|
|
expect(missingRate).to.be.lt(0.2) // less than 20% missing
|
|
})
|
|
})
|
|
})
|
|
|
|
it('no duplicate recipe names', () => {
|
|
cy.request('/api/recipes').then(res => {
|
|
const names = res.body.map(r => r.name)
|
|
const unique = new Set(names)
|
|
// Allow some duplicates but flag if many
|
|
const dupRate = (names.length - unique.size) / names.length
|
|
expect(dupRate).to.be.lt(0.1) // less than 10% duplicates
|
|
})
|
|
})
|
|
|
|
it('recipe costs are calculable', () => {
|
|
cy.request('/api/oils').then(oilRes => {
|
|
const oilPrices = {}
|
|
oilRes.body.forEach(o => {
|
|
oilPrices[o.name] = o.bottle_price / o.drop_count
|
|
})
|
|
cy.request('/api/recipes').then(recipeRes => {
|
|
recipeRes.body.slice(0, 20).forEach(recipe => {
|
|
let cost = 0
|
|
recipe.ingredients.forEach(ing => {
|
|
cost += (oilPrices[ing.oil_name] || 0) * ing.drops
|
|
})
|
|
expect(cost).to.be.a('number')
|
|
expect(cost).to.be.gte(0)
|
|
})
|
|
})
|
|
})
|
|
})
|
|
})
|