Skip to main content
Glama
kotlinTestAuthor.test.ts13.4 kB
import { expect } from "chai"; import * as sinon from "sinon"; import proxyquire from "proxyquire"; import { EventEmitter } from "events"; import { TestGenerationOptions } from "../../src/models/TestAuthoring"; import * as fs from "fs"; import * as path from "path"; import { KotlinTestAuthor } from "../../src/utils/kotlinTestAuthor"; describe("KotlinTestAuthor", function() { // Increase timeout for async tests this.timeout(5000); let bridgeModule: any; let bridge: any; let sandbox: sinon.SinonSandbox; let fsAccessStub: sinon.SinonStub; let spawnStub: sinon.SinonStub; beforeEach(() => { sandbox = sinon.createSandbox(); fsAccessStub = sandbox.stub(); spawnStub = sandbox.stub(); // Use proxyquire to inject stubs const module = proxyquire("../../src/utils/kotlinTestAuthor", { "fs/promises": { access: fsAccessStub }, "child_process": { spawn: spawnStub } }); bridgeModule = module.KotlinTestAuthor; bridge = new bridgeModule(); }); afterEach(() => { sandbox.restore(); }); describe("constructor", () => { it("should create instance with default values", () => { const defaultBridge = new bridgeModule(); expect(defaultBridge.getVersion()).to.equal("2.2.0"); expect(defaultBridge.getJarPath()).to.contain("/tmp/auto-mobile/kotlinpoet"); }); it("should create instance with custom version", () => { const customBridge = new bridgeModule("2.3.0"); expect(customBridge.getVersion()).to.equal("2.3.0"); expect(customBridge.getJarPath()).to.contain("kotlinpoet-jvm-2.3.0.jar"); }); it("should create instance with custom jar path", () => { const customBridge = new bridgeModule(undefined, "/custom/path/kotlinpoet.jar"); expect(customBridge.getJarPath()).to.equal("/custom/path/kotlinpoet.jar"); }); }); describe("isAvailable", () => { it("should return true when JAR file exists", async () => { fsAccessStub.resolves(); const result = await bridge.isAvailable(); expect(result).to.be.true; expect(fsAccessStub).to.have.been.calledOnce; }); it("should return false when JAR file does not exist", async () => { fsAccessStub.rejects(new Error("File not found")); const result = await bridge.isAvailable(); expect(result).to.be.false; expect(fsAccessStub).to.have.been.calledOnce; }); it("should return false when JAR path is not set", async () => { bridge.setJarPath(""); const result = await bridge.isAvailable(); expect(result).to.be.false; expect(fsAccessStub).not.to.have.been.called; }); }); describe("generateTest", () => { let mockProcess: any; beforeEach(() => { mockProcess = new EventEmitter() as any; mockProcess.stdout = new EventEmitter(); mockProcess.stderr = new EventEmitter(); spawnStub.returns(mockProcess); fsAccessStub.resolves(); // JAR is available }); it("should generate test successfully with KotlinPoet", async () => { const planPath = "/path/to/test.yaml"; const options: TestGenerationOptions = { testClassName: "TestClass", testPackage: "com.example.tests", kotlinTestOutputPath: "/output/path" }; const expectedResponse = { success: true, sourceCode: "package com.example.tests\n\nclass TestClass { }", className: "TestClass", testFilePath: "/output/path/TestClass.kt", testMethods: ["testMethod"] }; // Start the async operation const resultPromise = bridge.generateTest(planPath, options); // Wait a tick to ensure the promise has started await new Promise(resolve => setImmediate(resolve)); // Simulate successful process execution mockProcess.stdout.emit("data", JSON.stringify(expectedResponse)); mockProcess.emit("close", 0); const result = await resultPromise; expect(result.success).to.be.true; expect(result.sourceCode).to.equal(expectedResponse.sourceCode); expect(result.className).to.equal(expectedResponse.className); expect(result.testFilePath).to.equal(expectedResponse.testFilePath); expect(result.testMethods).to.deep.equal(expectedResponse.testMethods); }); it("should handle KotlinPoet process failure", async () => { const planPath = "/path/to/test.yaml"; const options: TestGenerationOptions = {}; const resultPromise = bridge.generateTest(planPath, options); // Wait a tick await new Promise(resolve => setImmediate(resolve)); // Simulate process failure mockProcess.stderr.emit("data", "Error: Failed to generate test"); mockProcess.emit("close", 1); const result = await resultPromise; expect(result.success).to.be.false; expect(result.message).to.contain("KotlinPoet process failed"); }); it("should handle JSON parsing errors", async () => { const planPath = "/path/to/test.yaml"; const options: TestGenerationOptions = {}; const resultPromise = bridge.generateTest(planPath, options); // Wait a tick await new Promise(resolve => setImmediate(resolve)); // Simulate invalid JSON response mockProcess.stdout.emit("data", "Invalid JSON"); mockProcess.emit("close", 0); const result = await resultPromise; expect(result.success).to.be.false; expect(result.message).to.contain("Failed to parse KotlinPoet response"); }); it("should handle spawn errors", async () => { spawnStub.throws(new Error("Failed to spawn process")); const planPath = "/path/to/test.yaml"; const options: TestGenerationOptions = {}; const result = await bridge.generateTest(planPath, options); expect(result.success).to.be.false; expect(result.message).to.contain("KotlinPoet generation failed"); }); it("should pass all options to KotlinPoet process", async () => { const planPath = "/path/to/test.yaml"; const options: TestGenerationOptions = { testClassName: "MyTest", testPackage: "com.example", kotlinTestOutputPath: "/output", useParameterizedTests: true, assertionStyle: "junit5" }; const resultPromise = bridge.generateTest(planPath, options); // Simulate successful response await new Promise(resolve => setImmediate(resolve)); mockProcess.stdout.emit("data", JSON.stringify({ success: true })); mockProcess.emit("close", 0); await resultPromise; expect(spawnStub).to.have.been.calledOnce; const [command, args] = spawnStub.firstCall.args; expect(command).to.equal("java"); expect(args).to.include("--plan"); expect(args).to.include(planPath); expect(args).to.include("--class"); expect(args).to.include("MyTest"); expect(args).to.include("--package"); expect(args).to.include("com.example"); expect(args).to.include("--output"); expect(args).to.include("/output"); expect(args).to.include("--parameterized"); expect(args).to.include("true"); expect(args).to.include("--assertion-style"); expect(args).to.include("junit5"); }); }); describe("setJarPath / getJarPath", () => { it("should set and get custom JAR path", () => { const customPath = "/custom/path/kotlinpoet.jar"; bridge.setJarPath(customPath); expect(bridge.getJarPath()).to.equal(customPath); }); }); describe("version management", () => { it("should set and get version", () => { bridge.setVersion("2.3.0"); expect(bridge.getVersion()).to.equal("2.3.0"); }); it("should update JAR path when version changes", () => { bridge.setVersion("2.3.0"); const jarPath = bridge.getJarPath(); expect(jarPath).to.equal("/tmp/auto-mobile/kotlinpoet/kotlinpoet-jvm-2.3.0.jar"); }); it("should not update JAR path when using custom path", () => { process.env.KOTLINPOET_JAR_PATH = "/custom/kotlinpoet.jar"; // Create new instance with env var set const module = proxyquire("../../src/utils/kotlinTestAuthor", { "fs/promises": { access: fsAccessStub }, "child_process": { spawn: spawnStub } }); const customBridge = new module.KotlinTestAuthor(); customBridge.setVersion("2.3.0"); expect(customBridge.getJarPath()).to.equal("/custom/kotlinpoet.jar"); delete process.env.KOTLINPOET_JAR_PATH; }); }); describe("environment variables", () => { it("should use KOTLINPOET_VERSION environment variable", () => { process.env.KOTLINPOET_VERSION = "2.1.0"; const module = proxyquire("../../src/utils/kotlinTestAuthor", { "fs/promises": { access: fsAccessStub }, "child_process": { spawn: spawnStub } }); const envBridge = new module.KotlinTestAuthor(); expect(envBridge.getVersion()).to.equal("2.1.0"); expect(envBridge.getJarPath()).to.equal("/tmp/auto-mobile/kotlinpoet/kotlinpoet-jvm-2.1.0.jar"); delete process.env.KOTLINPOET_VERSION; }); it("should use KOTLINPOET_JAR_PATH environment variable", () => { process.env.KOTLINPOET_JAR_PATH = "/env/path/kotlinpoet.jar"; const module = proxyquire("../../src/utils/kotlinTestAuthor", { "fs/promises": { access: fsAccessStub }, "child_process": { spawn: spawnStub } }); const envBridge = new module.KotlinTestAuthor(); expect(envBridge.getJarPath()).to.equal("/env/path/kotlinpoet.jar"); delete process.env.KOTLINPOET_JAR_PATH; }); }); describe("ensureAvailable", () => { const testJarPath = "/tmp/auto-mobile/kotlinpoet/kotlinpoet-jvm-2.2.0.jar"; beforeEach(async () => { // Clean up any existing test JAR before each test try { if (fs.existsSync(testJarPath)) { fs.unlinkSync(testJarPath); } // Also clean up the directory if empty const jarDir = path.dirname(testJarPath); if (fs.existsSync(jarDir)) { const files = fs.readdirSync(jarDir); if (files.length === 0) { fs.rmdirSync(jarDir); } } } catch (error) { // Ignore cleanup errors } }); afterEach(async () => { // Clean up after tests try { if (fs.existsSync(testJarPath)) { fs.unlinkSync(testJarPath); } } catch (error) { // Ignore cleanup errors } }); it("should return true if JAR already exists", async () => { // Create the directory and a dummy JAR file const jarDir = path.dirname(testJarPath); fs.mkdirSync(jarDir, { recursive: true }); fs.writeFileSync(testJarPath, "dummy jar content"); const bridge = new KotlinTestAuthor(); const result = await bridge.ensureAvailable(); expect(result).to.be.true; expect(fs.existsSync(testJarPath)).to.be.true; }); }); describe("process communication", () => { let mockProcess: any; beforeEach(() => { mockProcess = new EventEmitter() as any; mockProcess.stdout = new EventEmitter(); mockProcess.stderr = new EventEmitter(); spawnStub.returns(mockProcess); fsAccessStub.resolves(); }); it("should handle multi-chunk stdout data", async () => { const planPath = "/path/to/test.yaml"; const options: TestGenerationOptions = {}; const expectedResponse = { success: true, sourceCode: "test code" }; const resultPromise = bridge.generateTest(planPath, options); // Wait a tick await new Promise(resolve => setImmediate(resolve)); // Simulate response in multiple chunks mockProcess.stdout.emit("data", JSON.stringify(expectedResponse).substring(0, 10)); mockProcess.stdout.emit("data", JSON.stringify(expectedResponse).substring(10)); mockProcess.emit("close", 0); const result = await resultPromise; expect(result.success).to.be.true; expect(result.sourceCode).to.equal("test code"); }); it("should capture stderr output", async () => { const planPath = "/path/to/test.yaml"; const options: TestGenerationOptions = {}; const resultPromise = bridge.generateTest(planPath, options); // Wait a tick await new Promise(resolve => setImmediate(resolve)); // Simulate stderr output mockProcess.stderr.emit("data", "Warning: "); mockProcess.stderr.emit("data", "Something went wrong"); mockProcess.emit("close", 1); const result = await resultPromise; expect(result.success).to.be.false; expect(result.message).to.contain("Warning: Something went wrong"); }); it("should set UTF-8 encoding for Java process", async () => { const planPath = "/path/to/test.yaml"; const options: TestGenerationOptions = {}; const resultPromise = bridge.generateTest(planPath, options); await new Promise(resolve => setImmediate(resolve)); mockProcess.stdout.emit("data", JSON.stringify({ success: true })); mockProcess.emit("close", 0); await resultPromise; const spawnOptions = spawnStub.firstCall.args[2]; expect(spawnOptions.env.JAVA_TOOL_OPTIONS).to.equal("-Dfile.encoding=UTF-8"); }); }); });

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/zillow/auto-mobile'

If you have feedback or need assistance with the MCP directory API, please join our Discord server