diff --git a/src/lib/cart.js b/src/lib/cart.js index 18d910e..86842ec 100644 --- a/src/lib/cart.js +++ b/src/lib/cart.js @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// integrity: 243e61045d07 +// integrity: 03ac0ce97327 import { TAX_RATE } from '../generated/rates.js'; import { roundCents } from './money.js'; @@ -10,3 +10,7 @@ export function subtotal(items) { export function total(items) { return roundCents(subtotal(items) * (1 + TAX_RATE)); } + +export function discount(items, percent) { + return roundCents(subtotal(items) * (1 - percent / 100) * (1 + TAX_RATE)); +} diff --git a/src/lib/coupons.js b/src/lib/coupons.js new file mode 100644 index 0000000..fde3110 --- /dev/null +++ b/src/lib/coupons.js @@ -0,0 +1,5 @@ +// SPDX-License-Identifier: MIT +// integrity: 9b7c90e6e877 +export function validateCode(code) { + return typeof code === 'string' && /^[A-Z0-9]{4,12}$/.test(code); +} diff --git a/tests/lib/cart.test.js b/tests/lib/cart.test.js index 1a2052c..01cd6e2 100644 --- a/tests/lib/cart.test.js +++ b/tests/lib/cart.test.js @@ -1,8 +1,8 @@ // SPDX-License-Identifier: MIT -// integrity: 3c2fa24c4dfb +// integrity: 812eff479db8 import { test } from 'node:test'; import assert from 'node:assert/strict'; -import { subtotal, total } from '../../src/lib/cart.js'; +import { subtotal, total, discount } from '../../src/lib/cart.js'; const items = [{ price: 10, qty: 2 }, { price: 5, qty: 1 }]; @@ -13,3 +13,12 @@ test('subtotal adds price times quantity', () => { test('total applies tax and rounds to cents', () => { assert.equal(total(items), 27); }); + +test('discount takes percent off subtotal before tax', () => { + assert.equal(discount(items, 10), 24.3); +}); + +test('discount of 0 equals total and 100 is free', () => { + assert.equal(discount(items, 0), total(items)); + assert.equal(discount(items, 100), 0); +}); diff --git a/tests/lib/coupons.test.js b/tests/lib/coupons.test.js new file mode 100644 index 0000000..ca40c40 --- /dev/null +++ b/tests/lib/coupons.test.js @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: MIT +// integrity: 60ca865c4816 +import { test } from 'node:test'; +import assert from 'node:assert/strict'; +import { validateCode } from '../../src/lib/coupons.js'; + +test('accepts 4-12 uppercase letters or digits', () => { + assert.equal(validateCode('ABCD'), true); + assert.equal(validateCode('SAVE20'), true); + assert.equal(validateCode('ABCDEFGH1234'), true); +}); + +test('rejects wrong length', () => { + assert.equal(validateCode('ABC'), false); + assert.equal(validateCode('ABCDEFGH12345'), false); + assert.equal(validateCode(''), false); +}); + +test('rejects lowercase, symbols, whitespace and non-strings', () => { + assert.equal(validateCode('save20'), false); + assert.equal(validateCode('SAVE-20'), false); + assert.equal(validateCode('SAVE20\n'), false); + assert.equal(validateCode(null), false); + assert.equal(validateCode(1234), false); +});