mirror of
https://github.com/samiyev/puaros.git
synced 2025-12-27 23:06:54 +05:00
- Add onboarding module for pre-flight checks (Redis, Ollama, model, project) - Implement start command with TUI rendering and dependency injection - Implement init command for .ipuaro.json config file creation - Implement index command for standalone project indexing - Add CLI options: --auto-apply, --model, --help, --version - Register all 18 tools via tools-setup helper - Add 29 unit tests for CLI commands - Update CHANGELOG and ROADMAP for v0.15.0
354 lines
12 KiB
TypeScript
354 lines
12 KiB
TypeScript
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest"
|
|
import {
|
|
checkRedis,
|
|
checkOllama,
|
|
checkModel,
|
|
checkProjectSize,
|
|
runOnboarding,
|
|
} from "../../../../src/cli/commands/onboarding.js"
|
|
import { RedisClient } from "../../../../src/infrastructure/storage/RedisClient.js"
|
|
import { OllamaClient } from "../../../../src/infrastructure/llm/OllamaClient.js"
|
|
import { FileScanner } from "../../../../src/infrastructure/indexer/FileScanner.js"
|
|
|
|
vi.mock("../../../../src/infrastructure/storage/RedisClient.js")
|
|
vi.mock("../../../../src/infrastructure/llm/OllamaClient.js")
|
|
vi.mock("../../../../src/infrastructure/indexer/FileScanner.js")
|
|
|
|
describe("onboarding", () => {
|
|
beforeEach(() => {
|
|
vi.clearAllMocks()
|
|
})
|
|
|
|
afterEach(() => {
|
|
vi.restoreAllMocks()
|
|
})
|
|
|
|
describe("checkRedis", () => {
|
|
it("should return ok when Redis connects and pings successfully", async () => {
|
|
const mockConnect = vi.fn().mockResolvedValue(undefined)
|
|
const mockPing = vi.fn().mockResolvedValue(true)
|
|
const mockDisconnect = vi.fn().mockResolvedValue(undefined)
|
|
|
|
vi.mocked(RedisClient).mockImplementation(
|
|
() =>
|
|
({
|
|
connect: mockConnect,
|
|
ping: mockPing,
|
|
disconnect: mockDisconnect,
|
|
}) as unknown as RedisClient,
|
|
)
|
|
|
|
const result = await checkRedis({
|
|
host: "localhost",
|
|
port: 6379,
|
|
db: 0,
|
|
keyPrefix: "ipuaro:",
|
|
})
|
|
|
|
expect(result.ok).toBe(true)
|
|
expect(result.error).toBeUndefined()
|
|
expect(mockConnect).toHaveBeenCalled()
|
|
expect(mockPing).toHaveBeenCalled()
|
|
expect(mockDisconnect).toHaveBeenCalled()
|
|
})
|
|
|
|
it("should return error when Redis connection fails", async () => {
|
|
vi.mocked(RedisClient).mockImplementation(
|
|
() =>
|
|
({
|
|
connect: vi.fn().mockRejectedValue(new Error("Connection refused")),
|
|
}) as unknown as RedisClient,
|
|
)
|
|
|
|
const result = await checkRedis({
|
|
host: "localhost",
|
|
port: 6379,
|
|
db: 0,
|
|
keyPrefix: "ipuaro:",
|
|
})
|
|
|
|
expect(result.ok).toBe(false)
|
|
expect(result.error).toContain("Cannot connect to Redis")
|
|
})
|
|
|
|
it("should return error when ping fails", async () => {
|
|
vi.mocked(RedisClient).mockImplementation(
|
|
() =>
|
|
({
|
|
connect: vi.fn().mockResolvedValue(undefined),
|
|
ping: vi.fn().mockResolvedValue(false),
|
|
disconnect: vi.fn().mockResolvedValue(undefined),
|
|
}) as unknown as RedisClient,
|
|
)
|
|
|
|
const result = await checkRedis({
|
|
host: "localhost",
|
|
port: 6379,
|
|
db: 0,
|
|
keyPrefix: "ipuaro:",
|
|
})
|
|
|
|
expect(result.ok).toBe(false)
|
|
expect(result.error).toContain("Redis ping failed")
|
|
})
|
|
})
|
|
|
|
describe("checkOllama", () => {
|
|
it("should return ok when Ollama is available", async () => {
|
|
vi.mocked(OllamaClient).mockImplementation(
|
|
() =>
|
|
({
|
|
isAvailable: vi.fn().mockResolvedValue(true),
|
|
}) as unknown as OllamaClient,
|
|
)
|
|
|
|
const result = await checkOllama({
|
|
model: "qwen2.5-coder:7b-instruct",
|
|
contextWindow: 128_000,
|
|
temperature: 0.1,
|
|
host: "http://localhost:11434",
|
|
timeout: 120_000,
|
|
})
|
|
|
|
expect(result.ok).toBe(true)
|
|
expect(result.error).toBeUndefined()
|
|
})
|
|
|
|
it("should return error when Ollama is not available", async () => {
|
|
vi.mocked(OllamaClient).mockImplementation(
|
|
() =>
|
|
({
|
|
isAvailable: vi.fn().mockResolvedValue(false),
|
|
}) as unknown as OllamaClient,
|
|
)
|
|
|
|
const result = await checkOllama({
|
|
model: "qwen2.5-coder:7b-instruct",
|
|
contextWindow: 128_000,
|
|
temperature: 0.1,
|
|
host: "http://localhost:11434",
|
|
timeout: 120_000,
|
|
})
|
|
|
|
expect(result.ok).toBe(false)
|
|
expect(result.error).toContain("Cannot connect to Ollama")
|
|
})
|
|
})
|
|
|
|
describe("checkModel", () => {
|
|
it("should return ok when model is available", async () => {
|
|
vi.mocked(OllamaClient).mockImplementation(
|
|
() =>
|
|
({
|
|
hasModel: vi.fn().mockResolvedValue(true),
|
|
}) as unknown as OllamaClient,
|
|
)
|
|
|
|
const result = await checkModel({
|
|
model: "qwen2.5-coder:7b-instruct",
|
|
contextWindow: 128_000,
|
|
temperature: 0.1,
|
|
host: "http://localhost:11434",
|
|
timeout: 120_000,
|
|
})
|
|
|
|
expect(result.ok).toBe(true)
|
|
expect(result.needsPull).toBe(false)
|
|
})
|
|
|
|
it("should return needsPull when model is not available", async () => {
|
|
vi.mocked(OllamaClient).mockImplementation(
|
|
() =>
|
|
({
|
|
hasModel: vi.fn().mockResolvedValue(false),
|
|
}) as unknown as OllamaClient,
|
|
)
|
|
|
|
const result = await checkModel({
|
|
model: "qwen2.5-coder:7b-instruct",
|
|
contextWindow: 128_000,
|
|
temperature: 0.1,
|
|
host: "http://localhost:11434",
|
|
timeout: 120_000,
|
|
})
|
|
|
|
expect(result.ok).toBe(false)
|
|
expect(result.needsPull).toBe(true)
|
|
expect(result.error).toContain("not installed")
|
|
})
|
|
})
|
|
|
|
describe("checkProjectSize", () => {
|
|
it("should return ok when file count is within limits", async () => {
|
|
vi.mocked(FileScanner).mockImplementation(
|
|
() =>
|
|
({
|
|
scanAll: vi.fn().mockResolvedValue(
|
|
Array.from({ length: 100 }, (_, i) => ({
|
|
path: `file${String(i)}.ts`,
|
|
type: "file" as const,
|
|
size: 1000,
|
|
lastModified: Date.now(),
|
|
})),
|
|
),
|
|
}) as unknown as FileScanner,
|
|
)
|
|
|
|
const result = await checkProjectSize("/test/path")
|
|
|
|
expect(result.ok).toBe(true)
|
|
expect(result.fileCount).toBe(100)
|
|
expect(result.warning).toBeUndefined()
|
|
})
|
|
|
|
it("should return warning when file count exceeds limit", async () => {
|
|
vi.mocked(FileScanner).mockImplementation(
|
|
() =>
|
|
({
|
|
scanAll: vi.fn().mockResolvedValue(
|
|
Array.from({ length: 15000 }, (_, i) => ({
|
|
path: `file${String(i)}.ts`,
|
|
type: "file" as const,
|
|
size: 1000,
|
|
lastModified: Date.now(),
|
|
})),
|
|
),
|
|
}) as unknown as FileScanner,
|
|
)
|
|
|
|
const result = await checkProjectSize("/test/path", 10_000)
|
|
|
|
expect(result.ok).toBe(true)
|
|
expect(result.fileCount).toBe(15000)
|
|
expect(result.warning).toContain("15")
|
|
expect(result.warning).toContain("000 files")
|
|
})
|
|
|
|
it("should return error when no files found", async () => {
|
|
vi.mocked(FileScanner).mockImplementation(
|
|
() =>
|
|
({
|
|
scanAll: vi.fn().mockResolvedValue([]),
|
|
}) as unknown as FileScanner,
|
|
)
|
|
|
|
const result = await checkProjectSize("/test/path")
|
|
|
|
expect(result.ok).toBe(false)
|
|
expect(result.fileCount).toBe(0)
|
|
expect(result.warning).toContain("No supported files found")
|
|
})
|
|
})
|
|
|
|
describe("runOnboarding", () => {
|
|
it("should return success when all checks pass", async () => {
|
|
vi.mocked(RedisClient).mockImplementation(
|
|
() =>
|
|
({
|
|
connect: vi.fn().mockResolvedValue(undefined),
|
|
ping: vi.fn().mockResolvedValue(true),
|
|
disconnect: vi.fn().mockResolvedValue(undefined),
|
|
}) as unknown as RedisClient,
|
|
)
|
|
|
|
vi.mocked(OllamaClient).mockImplementation(
|
|
() =>
|
|
({
|
|
isAvailable: vi.fn().mockResolvedValue(true),
|
|
hasModel: vi.fn().mockResolvedValue(true),
|
|
}) as unknown as OllamaClient,
|
|
)
|
|
|
|
vi.mocked(FileScanner).mockImplementation(
|
|
() =>
|
|
({
|
|
scanAll: vi.fn().mockResolvedValue([{ path: "file.ts" }]),
|
|
}) as unknown as FileScanner,
|
|
)
|
|
|
|
const result = await runOnboarding({
|
|
redisConfig: { host: "localhost", port: 6379, db: 0, keyPrefix: "ipuaro:" },
|
|
llmConfig: {
|
|
model: "test",
|
|
contextWindow: 128_000,
|
|
temperature: 0.1,
|
|
host: "http://localhost:11434",
|
|
timeout: 120_000,
|
|
},
|
|
projectPath: "/test/path",
|
|
})
|
|
|
|
expect(result.success).toBe(true)
|
|
expect(result.redisOk).toBe(true)
|
|
expect(result.ollamaOk).toBe(true)
|
|
expect(result.modelOk).toBe(true)
|
|
expect(result.projectOk).toBe(true)
|
|
expect(result.errors).toHaveLength(0)
|
|
})
|
|
|
|
it("should return failure when Redis fails", async () => {
|
|
vi.mocked(RedisClient).mockImplementation(
|
|
() =>
|
|
({
|
|
connect: vi.fn().mockRejectedValue(new Error("Connection refused")),
|
|
}) as unknown as RedisClient,
|
|
)
|
|
|
|
vi.mocked(OllamaClient).mockImplementation(
|
|
() =>
|
|
({
|
|
isAvailable: vi.fn().mockResolvedValue(true),
|
|
hasModel: vi.fn().mockResolvedValue(true),
|
|
}) as unknown as OllamaClient,
|
|
)
|
|
|
|
vi.mocked(FileScanner).mockImplementation(
|
|
() =>
|
|
({
|
|
scanAll: vi.fn().mockResolvedValue([{ path: "file.ts" }]),
|
|
}) as unknown as FileScanner,
|
|
)
|
|
|
|
const result = await runOnboarding({
|
|
redisConfig: { host: "localhost", port: 6379, db: 0, keyPrefix: "ipuaro:" },
|
|
llmConfig: {
|
|
model: "test",
|
|
contextWindow: 128_000,
|
|
temperature: 0.1,
|
|
host: "http://localhost:11434",
|
|
timeout: 120_000,
|
|
},
|
|
projectPath: "/test/path",
|
|
})
|
|
|
|
expect(result.success).toBe(false)
|
|
expect(result.redisOk).toBe(false)
|
|
expect(result.errors.length).toBeGreaterThan(0)
|
|
})
|
|
|
|
it("should skip checks when skip options are set", async () => {
|
|
const result = await runOnboarding({
|
|
redisConfig: { host: "localhost", port: 6379, db: 0, keyPrefix: "ipuaro:" },
|
|
llmConfig: {
|
|
model: "test",
|
|
contextWindow: 128_000,
|
|
temperature: 0.1,
|
|
host: "http://localhost:11434",
|
|
timeout: 120_000,
|
|
},
|
|
projectPath: "/test/path",
|
|
skipRedis: true,
|
|
skipOllama: true,
|
|
skipModel: true,
|
|
skipProject: true,
|
|
})
|
|
|
|
expect(result.success).toBe(true)
|
|
expect(result.redisOk).toBe(true)
|
|
expect(result.ollamaOk).toBe(true)
|
|
expect(result.modelOk).toBe(true)
|
|
expect(result.projectOk).toBe(true)
|
|
})
|
|
})
|
|
})
|