diff --git a/src/lib/credits.js b/src/lib/credits.js new file mode 100644 index 0000000..270308e --- /dev/null +++ b/src/lib/credits.js @@ -0,0 +1,36 @@ +"use strict"; + +function assertPositiveInt(name, value) { + if (!Number.isInteger(value) || value <= 0) { + throw new Error(`${name} must be a positive integer`); + } +} + +function validateCreditConfig(config) { + assertPositiveInt("baseCredits", config.baseCredits); + assertPositiveInt("includedChars", config.includedChars); + assertPositiveInt("stepChars", config.stepChars); + assertPositiveInt("stepCredits", config.stepCredits); + assertPositiveInt("maxCharsPerArticle", config.maxCharsPerArticle); +} + +function calculateCredits(charCount, config) { + validateCreditConfig(config); + + if (!Number.isInteger(charCount) || charCount <= 0) { + throw new Error("charCount must be a positive integer"); + } + + if (charCount > config.maxCharsPerArticle) { + throw new Error("article_too_long"); + } + + const overage = Math.max(0, charCount - config.includedChars); + const extraSteps = overage === 0 ? 0 : Math.ceil(overage / config.stepChars); + return config.baseCredits + (extraSteps * config.stepCredits); +} + +module.exports = { + calculateCredits, + validateCreditConfig, +}; diff --git a/test/credits.test.js b/test/credits.test.js new file mode 100644 index 0000000..4d29a49 --- /dev/null +++ b/test/credits.test.js @@ -0,0 +1,35 @@ +"use strict"; + +const test = require("node:test"); +const assert = require("node:assert/strict"); +const { calculateCredits } = require("../src/lib/credits"); + +const config = { + baseCredits: 1, + includedChars: 25000, + stepChars: 10000, + stepCredits: 1, + maxCharsPerArticle: 120000, +}; + +test("charges base credit up to included chars", () => { + assert.equal(calculateCredits(1, config), 1); + assert.equal(calculateCredits(25000, config), 1); +}); + +test("charges one extra credit after crossing step boundary", () => { + assert.equal(calculateCredits(25001, config), 2); + assert.equal(calculateCredits(35000, config), 2); +}); + +test("charges multiple steps correctly", () => { + assert.equal(calculateCredits(45001, config), 4); +}); + +test("rejects oversized articles", () => { + assert.throws(() => calculateCredits(120001, config), /article_too_long/); +}); + +test("rejects non-positive charCount", () => { + assert.throws(() => calculateCredits(0, config), /charCount must be a positive integer/); +});