From e8f7410a0a347a27cc2bc58eed85df48dde6a3f8 Mon Sep 17 00:00:00 2001 From: Fei Yan Date: Wed, 27 May 2026 07:30:50 +0800 Subject: [PATCH] test(pricing): verify MODEL_COSTS entries and promotional notes Add tests/test-pricing.ts to assert that known models have non-zero pricing, promotional deals are documented in comments, and Claude models include cache pricing. Fix test-pi-local to match against combined stdout+stderr since pi may output model listing to either stream. --- package-lock.json | 4 +- package.json | 5 ++- tests/test-pi-local.mjs | 7 +-- tests/test-pricing.ts | 96 +++++++++++++++++++++++++++++++++++++++++ 4 files changed, 105 insertions(+), 7 deletions(-) create mode 100644 tests/test-pricing.ts diff --git a/package-lock.json b/package-lock.json index cbcca43..09ef85f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "pi-commandcode-provider", - "version": "0.1.1", + "version": "0.2.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "pi-commandcode-provider", - "version": "0.1.1", + "version": "0.2.0", "license": "MIT", "dependencies": { "@mariozechner/pi-ai": "0.72.0" diff --git a/package.json b/package.json index 72a1cf4..515786a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "pi-commandcode-provider", - "version": "0.1.1", + "version": "0.2.0", "description": "pi custom provider for Command Code API (commandcode.ai)", "type": "module", "keywords": [ @@ -28,12 +28,13 @@ "LICENSE" ], "scripts": { - "test": "npm run typecheck && tsx tests/test-pure-functions.ts && tsx tests/test-models.ts && tsx tests/test-oauth.ts && tsx tests/test-abort.ts && tsx tests/test-stream.ts && node tests/test-pi-local.mjs", + "test": "npm run typecheck && tsx tests/test-pure-functions.ts && tsx tests/test-models.ts && tsx tests/test-pricing.ts && tsx tests/test-oauth.ts && tsx tests/test-abort.ts && tsx tests/test-stream.ts && node tests/test-pi-local.mjs", "typecheck": "tsc --noEmit", "format:check": "prettier --check '**/*.{ts,mjs,json,md}'", "format": "prettier --write '**/*.{ts,mjs,json,md}'", "test:unit": "tsx tests/test-pure-functions.ts", "test:models": "tsx tests/test-models.ts", + "test:pricing": "tsx tests/test-pricing.ts", "test:oauth": "tsx tests/test-oauth.ts", "test:abort": "tsx tests/test-abort.ts", "test:stream": "tsx tests/test-stream.ts", diff --git a/tests/test-pi-local.mjs b/tests/test-pi-local.mjs index 8b37560..f053971 100644 --- a/tests/test-pi-local.mjs +++ b/tests/test-pi-local.mjs @@ -294,9 +294,10 @@ try { modelListRequestCount = 0 const list = await runPi(["--no-extensions", "-e", EXT_PATH, "--list-models"], 20_000) assert.equal(list.code, 0, list.stderr) - assert.match(list.stdout, /commandcode/) - assert.match(list.stdout, /deepseek\/deepseek-v4-flash/) - assert.match(list.stdout, /Qwen\/Qwen3\.7-Max/) + const listOutput = `${list.stdout}\n${list.stderr}` + assert.match(listOutput, /commandcode/) + assert.match(listOutput, /deepseek\/deepseek-v4-flash/) + assert.match(listOutput, /Qwen\/Qwen3\.7-Max/) assert.equal(modelListRequestCount, 1) console.log("[pi-local] print mode through real extension and mock API") diff --git a/tests/test-pricing.ts b/tests/test-pricing.ts new file mode 100644 index 0000000..702c966 --- /dev/null +++ b/tests/test-pricing.ts @@ -0,0 +1,96 @@ +import assert from "node:assert/strict" +import { describe, it } from "node:test" + +// MODEL_COSTS is a module-level const in index.ts. We verify the pricing +// overlay by importing the map through a dedicated re-export so tests don't +// need to spin up the full extension. +// +// To keep the test self-contained without importing the full extension (which +// requires ExtensionAPI), we read the source and extract the constant at +// runtime. A cleaner approach would be a dedicated src/pricing.ts module, +// but for now we verify the known cost entries directly. + +import { readFileSync } from "node:fs" +import { resolve, dirname } from "node:path" +import { fileURLToPath } from "node:url" + +const __dirname = dirname(fileURLToPath(import.meta.url)) +const indexSource = readFileSync(resolve(__dirname, "..", "index.ts"), "utf-8") + +// Extract MODEL_COSTS object from index.ts source using a simple parse. +// The map is written as a Record +// so we eval it in a sandboxed context. +const match = indexSource.match( + /const MODEL_COSTS:\s*Record\s*=\s*\{([\s\S]*?)\n\}/, +) +assert.ok(match, "MODEL_COSTS constant should exist in index.ts") + +// Parse the cost entries from the extracted block. +const costBlock = match[1] +const entries: Record = {} +for (const line of costBlock.split("\n")) { + const trimmed = line.trim() + if (!trimmed || trimmed.startsWith("//")) continue + const entryMatch = trimmed.match( + /^"([^"]+)":\s*\{\s*input:\s*([\d.]+),\s*output:\s*([\d.]+)/, + ) + if (entryMatch) { + entries[entryMatch[1]] = { + input: Number(entryMatch[2]), + output: Number(entryMatch[3]), + } + } +} + +describe("MODEL_COSTS pricing overlay", () => { + it("covers known Command Code models with non-zero pricing", () => { + const knownModels = [ + "deepseek/deepseek-v4-flash", + "deepseek/deepseek-v4-pro", + "claude-sonnet-4-6", + "claude-opus-4-7", + "Qwen/Qwen3.7-Max", + "gpt-5.5", + "stepfun/Step-3.5-Flash", + ] + + for (const id of knownModels) { + const cost = entries[id] + assert.ok(cost, `MODEL_COSTS should include "${id}"`) + assert.ok(cost.input > 0, `"${id}" input cost should be > 0`) + assert.ok(cost.output > 0, `"${id}" output cost should be > 0`) + } + }) + + it("includes promotional pricing notes in comments", () => { + // The DeepSeek V4 Pro 4× deal and Qwen 3.7 Max 2× deal should be + // documented in the source comments. + assert.ok( + costBlock.includes("4× usage deal") || costBlock.includes("75% off"), + "DeepSeek V4 Pro promotional pricing should be documented", + ) + assert.ok( + costBlock.includes("2× usage deal") || costBlock.includes("50% off"), + "Qwen 3.7 Max promotional pricing should be documented", + ) + }) + + it("has cache pricing for models that support it", () => { + // Claude models should have non-zero cacheRead and cacheWrite costs. + const claudeModels = ["claude-sonnet-4-6", "claude-opus-4-7"] + for (const id of claudeModels) { + const fullEntryMatch = costBlock.match( + new RegExp(`"${id.replace(/\//g, "\\\\")}":\\s*\\{[^}]+cacheRead:\\s*([\\d.]+)[^}]+cacheWrite:\\s*([\\d.]+)`), + ) + assert.ok(fullEntryMatch, `"${id}" should have cacheRead and cacheWrite fields`) + assert.ok( + Number(fullEntryMatch[1]) > 0, + `"${id}" cacheRead should be > 0`, + ) + assert.ok( + Number(fullEntryMatch[2]) > 0, + `"${id}" cacheWrite should be > 0`, + ) + } + }) +}) \ No newline at end of file