Add tests and fix configuration

This commit is contained in:
Saoud Rizwan 2024-12-24 18:37:12 -08:00
parent 06dab2c3df
commit 897de134f7
5 changed files with 235 additions and 3 deletions

91
src/shared/array.test.ts Normal file
View File

@ -0,0 +1,91 @@
import { describe, it } from "mocha"
import "should"
import { findLastIndex, findLast } from "./array"
describe("Array Utilities", () => {
describe("findLastIndex", () => {
it("should find last matching element's index", () => {
const array = [1, 2, 3, 2, 1]
const index = findLastIndex(array, (x) => x === 2)
index.should.equal(3) // last '2' is at index 3
})
it("should return -1 when no element matches", () => {
const array = [1, 2, 3]
const index = findLastIndex(array, (x) => x === 4)
index.should.equal(-1)
})
it("should handle empty arrays", () => {
const array: number[] = []
const index = findLastIndex(array, (x) => x === 1)
index.should.equal(-1)
})
it("should work with different types", () => {
const array = ["a", "b", "c", "b", "a"]
const index = findLastIndex(array, (x) => x === "b")
index.should.equal(3)
})
it("should provide correct index in predicate", () => {
const array = [1, 2, 3]
const indices: number[] = []
findLastIndex(array, (_, index) => {
indices.push(index)
return false
})
indices.should.deepEqual([2, 1, 0]) // Should iterate in reverse
})
it("should provide array reference in predicate", () => {
const array = [1, 2, 3]
findLastIndex(array, (_, __, arr) => {
arr.should.equal(array) // Should pass original array
return false
})
})
})
describe("findLast", () => {
it("should find last matching element", () => {
const array = [1, 2, 3, 2, 1]
const element = findLast(array, (x) => x === 2)
should(element).not.be.undefined()
element!.should.equal(2)
})
it("should return undefined when no element matches", () => {
const array = [1, 2, 3]
const element = findLast(array, (x) => x === 4)
should(element).be.undefined()
})
it("should handle empty arrays", () => {
const array: number[] = []
const element = findLast(array, (x) => x === 1)
should(element).be.undefined()
})
it("should work with object arrays", () => {
const array = [
{ id: 1, value: "a" },
{ id: 2, value: "b" },
{ id: 3, value: "a" },
]
const element = findLast(array, (x) => x.value === "a")
should(element).not.be.undefined()
element!.should.deepEqual({ id: 3, value: "a" })
})
it("should provide correct index in predicate", () => {
const array = [1, 2, 3]
const indices: number[] = []
findLast(array, (_, index) => {
indices.push(index)
return false
})
indices.should.deepEqual([2, 1, 0]) // Should iterate in reverse
})
})
})

67
src/utils/cost.test.ts Normal file
View File

@ -0,0 +1,67 @@
import { describe, it } from "mocha"
import "should"
import { calculateApiCost } from "./cost"
import { ModelInfo } from "../shared/api"
describe("Cost Utilities", () => {
describe("calculateApiCost", () => {
it("should calculate basic input/output costs", () => {
const modelInfo: ModelInfo = {
supportsPromptCache: false,
inputPrice: 3.0, // $3 per million tokens
outputPrice: 15.0, // $15 per million tokens
}
const cost = calculateApiCost(modelInfo, 1000, 500)
// Input: (3.0 / 1_000_000) * 1000 = 0.003
// Output: (15.0 / 1_000_000) * 500 = 0.0075
// Total: 0.003 + 0.0075 = 0.0105
cost.should.equal(0.0105)
})
it("should handle missing prices", () => {
const modelInfo: ModelInfo = {
supportsPromptCache: true,
// No prices specified
}
const cost = calculateApiCost(modelInfo, 1000, 500)
cost.should.equal(0)
})
it("should use real model configuration (Claude 3.5 Sonnet)", () => {
const modelInfo: ModelInfo = {
maxTokens: 8192,
contextWindow: 200_000,
supportsImages: true,
supportsComputerUse: true,
supportsPromptCache: true,
inputPrice: 3.0,
outputPrice: 15.0,
cacheWritesPrice: 3.75,
cacheReadsPrice: 0.3,
}
const cost = calculateApiCost(modelInfo, 2000, 1000, 1500, 500)
// Cache writes: (3.75 / 1_000_000) * 1500 = 0.005625
// Cache reads: (0.3 / 1_000_000) * 500 = 0.00015
// Input: (3.0 / 1_000_000) * 2000 = 0.006
// Output: (15.0 / 1_000_000) * 1000 = 0.015
// Total: 0.005625 + 0.00015 + 0.006 + 0.015 = 0.026775
cost.should.equal(0.026775)
})
it("should handle zero token counts", () => {
const modelInfo: ModelInfo = {
supportsPromptCache: true,
inputPrice: 3.0,
outputPrice: 15.0,
cacheWritesPrice: 3.75,
cacheReadsPrice: 0.3,
}
const cost = calculateApiCost(modelInfo, 0, 0, 0, 0)
cost.should.equal(0)
})
})
})

71
src/utils/fs.test.ts Normal file
View File

@ -0,0 +1,71 @@
import * as fs from "fs/promises"
import { after, describe, it } from "mocha"
import * as os from "os"
import * as path from "path"
import "should"
import { createDirectoriesForFile, fileExistsAtPath } from "./fs"
describe("Filesystem Utilities", () => {
const tmpDir = path.join(os.tmpdir(), "cline-test-" + Math.random().toString(36).slice(2))
// Clean up after tests
after(async () => {
try {
await fs.rm(tmpDir, { recursive: true, force: true })
} catch {
// Ignore cleanup errors
}
})
describe("fileExistsAtPath", () => {
it("should return true for existing paths", async () => {
await fs.mkdir(tmpDir, { recursive: true })
const testFile = path.join(tmpDir, "test.txt")
await fs.writeFile(testFile, "test")
const exists = await fileExistsAtPath(testFile)
exists.should.be.true()
})
it("should return false for non-existing paths", async () => {
const nonExistentPath = path.join(tmpDir, "does-not-exist.txt")
const exists = await fileExistsAtPath(nonExistentPath)
exists.should.be.false()
})
})
describe("createDirectoriesForFile", () => {
it("should create all necessary directories", async () => {
const deepPath = path.join(tmpDir, "deep", "nested", "dir", "file.txt")
const createdDirs = await createDirectoriesForFile(deepPath)
// Verify directories were created
createdDirs.length.should.be.greaterThan(0)
for (const dir of createdDirs) {
const exists = await fileExistsAtPath(dir)
exists.should.be.true()
}
})
it("should handle existing directories", async () => {
const existingDir = path.join(tmpDir, "existing")
await fs.mkdir(existingDir, { recursive: true })
const filePath = path.join(existingDir, "file.txt")
const createdDirs = await createDirectoriesForFile(filePath)
// Should not create any new directories
createdDirs.length.should.equal(0)
})
it("should normalize paths", async () => {
const unnormalizedPath = path.join(tmpDir, "a", "..", "b", ".", "file.txt")
const createdDirs = await createDirectoriesForFile(unnormalizedPath)
// Should create only the necessary directory
createdDirs.length.should.equal(1)
const exists = await fileExistsAtPath(path.join(tmpDir, "b"))
exists.should.be.true()
})
})
})

View File

@ -8,7 +8,7 @@
"compilerOptions": {
"module": "commonjs",
"moduleResolution": "node",
"types": ["node", "mocha", "vscode"]
"types": ["node", "mocha", "should", "vscode"]
},
"include": ["src/**/*.test.ts"]
}

View File

@ -34,8 +34,11 @@ function getAllFiles(dir) {
if (fs.statSync(filePath).isDirectory()) {
files = files.concat(getAllFiles(filePath))
} else {
const withoutExtension = path.join(dir, path.parse(file).name)
files.push(withoutExtension)
// Skip test files
if (!file.endsWith(".test.ts")) {
const withoutExtension = path.join(dir, path.parse(file).name)
files.push(withoutExtension)
}
}
})
return files