ai-pp3
Version:
CLI tool combining multimodal AI analysis with RawTherapee's engine to generate optimized PP3 profiles for RAW photography
259 lines • 10.6 kB
JavaScript
import { describe, expect, it, vi, beforeEach, afterEach } from "vitest";
import path from "node:path";
import fs from "node:fs";
// Mock only external dependencies
vi.mock("../raw-therapee-wrap.js", () => ({
convertDngToImageWithPP3: vi.fn(),
}));
vi.mock("../agent.js", () => ({
generatePP3FromRawImage: vi.fn(),
generateMultiPP3FromRawImage: vi.fn(),
}));
// Mock fs.promises.writeFile and copyFile to prevent actual file operations
vi.spyOn(fs.promises, "writeFile").mockImplementation(() => {
return Promise.resolve(undefined);
});
vi.spyOn(fs.promises, "copyFile").mockImplementation(() => {
return Promise.resolve(undefined);
});
// Mock process.exit to prevent it from actually exiting during tests
vi.stubGlobal("process", {
...process,
exit: vi.fn(),
});
// Import after mocking
const { processImage } = await import("../bin.js");
const { convertDngToImageWithPP3 } = await import("../raw-therapee-wrap.js");
const { generatePP3FromRawImage, generateMultiPP3FromRawImage } = await import("../agent.js");
// Use real test files
const TEST_INPUT_FILE = path.resolve("examples/1/IMG_0080.CR2");
const TEST_OUTPUT_DIR = path.resolve("test-temp");
beforeEach(() => {
vi.resetAllMocks();
// Mock generatePP3FromRawImage to return sample PP3 content
vi.mocked(generatePP3FromRawImage).mockResolvedValue("sample pp3 content");
// Mock convertDngToImageWithPP3 to succeed
vi.mocked(convertDngToImageWithPP3).mockResolvedValue();
// Reset the file operation mocks
vi.mocked(fs.promises.writeFile).mockImplementation(() => {
return Promise.resolve(undefined);
});
vi.mocked(fs.promises.copyFile).mockImplementation(() => {
return Promise.resolve(undefined);
});
});
afterEach(() => {
// Clean up any files that might have been created in the examples folder
const potentialFile = path.resolve("examples/1/IMG_0080.pp3");
try {
if (fs.existsSync(potentialFile)) {
fs.unlinkSync(potentialFile);
}
}
catch {
// Ignore cleanup errors
}
});
describe("processImage", () => {
it("should throw error for empty input path", async () => {
await expect(processImage("")).rejects.toThrow("Input path cannot be empty");
});
it("should throw error when input file not found", async () => {
await expect(processImage("/nonexistent/file.dng")).rejects.toThrow("Input file not found: /nonexistent/file.dng");
});
it("should process image in single generation mode", async () => {
const outputPath = path.join(TEST_OUTPUT_DIR, "output.pp3");
await processImage(TEST_INPUT_FILE, {
output: outputPath,
});
expect(generatePP3FromRawImage).toHaveBeenCalledWith({
inputPath: TEST_INPUT_FILE,
basePP3Path: undefined,
providerName: "openai",
visionModel: "gpt-4-vision-preview",
verbose: undefined,
keepPreview: undefined,
prompt: undefined,
preset: undefined,
sections: undefined,
previewQuality: undefined,
previewFormat: undefined,
maxRetries: undefined,
generations: undefined,
});
expect(convertDngToImageWithPP3).toHaveBeenCalled();
// Check that the PP3 file write was attempted
expect(fs.promises.writeFile).toHaveBeenCalledWith(outputPath, "sample pp3 content");
});
it("should handle PP3-only mode", async () => {
const outputPath = path.join(TEST_OUTPUT_DIR, "pp3-only.pp3");
await processImage(TEST_INPUT_FILE, {
output: outputPath,
pp3Only: true,
});
expect(generatePP3FromRawImage).toHaveBeenCalled();
expect(convertDngToImageWithPP3).not.toHaveBeenCalled();
});
it("should handle multi-generation mode", async () => {
const mockMultiResult = {
bestResult: {
generationIndex: 1,
pp3Content: "best pp3 content",
pp3Path: "/path/to/best.pp3",
processedImagePath: "/path/to/best_processed.jpg",
evaluationImagePath: "/path/to/best_eval.jpg",
success: true,
},
allResults: [
{
generationIndex: 0,
pp3Content: "pp3 content 1",
pp3Path: "/path/to/gen1.pp3",
processedImagePath: "/path/to/gen1_processed.jpg",
evaluationImagePath: "/path/to/gen1_eval.jpg",
success: true,
},
{
generationIndex: 1,
pp3Content: "best pp3 content",
pp3Path: "/path/to/best.pp3",
processedImagePath: "/path/to/best_processed.jpg",
evaluationImagePath: "/path/to/best_eval.jpg",
success: true,
},
],
evaluationReason: "Generation 2 has better exposure and color balance",
finalOutputPath: "/path/to/final.jpg",
};
vi.mocked(generateMultiPP3FromRawImage).mockResolvedValue(mockMultiResult);
const outputPath = path.join(TEST_OUTPUT_DIR, "multi-gen.pp3");
await processImage(TEST_INPUT_FILE, {
output: outputPath,
generations: 2,
verbose: true,
});
expect(generateMultiPP3FromRawImage).toHaveBeenCalledWith({
inputPath: TEST_INPUT_FILE,
basePP3Path: undefined,
providerName: "openai",
visionModel: "gpt-4-vision-preview",
verbose: true,
keepPreview: undefined,
prompt: undefined,
preset: undefined,
sections: undefined,
previewQuality: undefined,
previewFormat: undefined,
maxRetries: undefined,
generations: 2,
outputFormat: "jpeg",
outputQuality: undefined,
tiffCompression: undefined,
bitDepth: Number.NaN,
});
});
it("should handle multi-generation PP3-only mode", async () => {
const mockMultiResult = {
bestResult: {
generationIndex: 0,
pp3Content: "best pp3 content",
pp3Path: path.join(TEST_OUTPUT_DIR, "best.pp3"),
processedImagePath: path.join(TEST_OUTPUT_DIR, "best_processed.jpg"),
evaluationImagePath: path.join(TEST_OUTPUT_DIR, "best_eval.jpg"),
success: true,
},
allResults: [],
evaluationReason: "Only one generation",
finalOutputPath: path.join(TEST_OUTPUT_DIR, "final.jpg"),
};
vi.mocked(generateMultiPP3FromRawImage).mockResolvedValue(mockMultiResult);
const outputPath = path.join(TEST_OUTPUT_DIR, "multi-gen-pp3-only.pp3");
await processImage(TEST_INPUT_FILE, {
output: outputPath,
generations: 2,
pp3Only: true,
});
expect(generateMultiPP3FromRawImage).toHaveBeenCalled();
});
it("should handle different output formats", async () => {
const outputPath = path.join(TEST_OUTPUT_DIR, "tiff-format.pp3");
await processImage(TEST_INPUT_FILE, {
output: outputPath,
tiff: true,
});
expect(convertDngToImageWithPP3).toHaveBeenCalledWith(expect.objectContaining({
format: "tiff",
}));
});
it("should handle PNG format", async () => {
const outputPath = path.join(TEST_OUTPUT_DIR, "png-format.pp3");
await processImage(TEST_INPUT_FILE, {
output: outputPath,
png: true,
});
expect(convertDngToImageWithPP3).toHaveBeenCalledWith(expect.objectContaining({
format: "png",
}));
});
it("should default to JPEG format", async () => {
const outputPath = path.join(TEST_OUTPUT_DIR, "jpeg-format.pp3");
await processImage(TEST_INPUT_FILE, {
output: outputPath,
});
expect(convertDngToImageWithPP3).toHaveBeenCalledWith(expect.objectContaining({
format: "jpeg",
}));
});
it("should handle sections parameter", async () => {
const outputPath = path.join(TEST_OUTPUT_DIR, "sections.pp3");
await processImage(TEST_INPUT_FILE, {
output: outputPath,
sections: "Exposure,ColorToning,Detail",
});
expect(generatePP3FromRawImage).toHaveBeenCalledWith(expect.objectContaining({
sections: ["Exposure", "ColorToning", "Detail"],
}));
});
it("should filter empty sections", async () => {
const outputPath = path.join(TEST_OUTPUT_DIR, "filtered-sections.pp3");
await processImage(TEST_INPUT_FILE, {
output: outputPath,
sections: "Exposure, ,ColorToning, ",
});
expect(generatePP3FromRawImage).toHaveBeenCalledWith(expect.objectContaining({
sections: ["Exposure", "ColorToning"],
}));
});
it("should throw error when PP3 generation fails", async () => {
vi.mocked(generatePP3FromRawImage).mockRejectedValue(new Error("PP3 generation failed"));
const outputPath = path.join(TEST_OUTPUT_DIR, "error-test.pp3");
await expect(processImage(TEST_INPUT_FILE, { output: outputPath })).rejects.toThrow("PP3 generation failed");
});
it("should handle custom provider and model options", async () => {
const outputPath = path.join(TEST_OUTPUT_DIR, "custom-provider.pp3");
await processImage(TEST_INPUT_FILE, {
output: outputPath,
provider: "anthropic",
model: "claude-3-opus",
preset: "creative",
maxRetries: 5,
});
expect(generatePP3FromRawImage).toHaveBeenCalledWith(expect.objectContaining({
providerName: "anthropic",
visionModel: "claude-3-opus",
preset: "creative",
maxRetries: 5,
}));
});
it("should handle bit depth conversion", async () => {
const outputPath = path.join(TEST_OUTPUT_DIR, "bit-depth.pp3");
await processImage(TEST_INPUT_FILE, {
output: outputPath,
bitDepth: 8,
});
expect(convertDngToImageWithPP3).toHaveBeenCalledWith(expect.objectContaining({
bitDepth: 8,
}));
});
});
//# sourceMappingURL=bin.test.js.map