This is an automated email from the ASF dual-hosted git repository.

asf-gitbox-commits pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/cayenne.git

commit 3d7659ff2036bc005784ce109184748944840ed8
Author: Andrus Adamchik <[email protected]>
AuthorDate: Sun May 24 11:50:54 2026 -0400

    CAY-2949 CayenneModeler MCP: dbimport_run tool
    
    tests
---
 .claude/rules/cayenne-mcp-server/testing.md        |  43 ++++
 .../java/org/apache/cayenne/mcp/TestMcpClient.java | 141 ++++++++++++
 ...{InProcessMcpServer.java => TestMcpServer.java} |   8 +-
 .../cayenne/mcp/tools/cgen/CgenRunMcpIT.java       | 108 ++--------
 .../mcp/tools/dbimport/DbImportRunMcpIT.java       | 237 +++++++++++++++++++++
 5 files changed, 444 insertions(+), 93 deletions(-)

diff --git a/.claude/rules/cayenne-mcp-server/testing.md 
b/.claude/rules/cayenne-mcp-server/testing.md
new file mode 100644
index 000000000..6c5a0c04d
--- /dev/null
+++ b/.claude/rules/cayenne-mcp-server/testing.md
@@ -0,0 +1,43 @@
+---
+paths:
+  - "cayenne-mcp-server/**"
+description: cayenne-mcp-server testing conventions
+---
+
+## Integration Test Conventions
+
+### `TestMcpClient` — shared test extension
+
+`TestMcpClient` is a JUnit 5 extension (`BeforeEachCallback` + 
`AfterEachCallback`). Declare it as
+an instance field annotated with `@RegisterExtension`; JUnit starts the server 
before each test
+and shuts it down after:
+
+```java
+@RegisterExtension
+TestMcpClient client = new TestMcpClient();
+```
+
+Key methods:
+
+| Method                                            | Purpose |
+|---------------------------------------------------|---|
+| `client.send(json)`                               | Send a raw JSON-RPC 
message |
+| `client.sendToolCall(id, tool, project, dataMap)` | Send a `tools/call` 
request for tools that take `projectPath` + `dataMap` args |
+| `client.readLine(ms)`                             | Read next non-blank 
response line, fail after timeout |
+| `client.readLineAsJson(ms)`                       | Read next response line 
and return pretty-printed tool-result JSON |
+
+### Full JSON comparisons
+
+Prefer comparing the entire pretty-printed JSON payload as a string rather 
than asserting
+individual fields. Variable parts (file paths, JDBC URLs) are formatted in 
with `%s`; everything
+else is written literally. This makes assertions self-documenting and catches 
unexpected field
+additions or serialization regressions that field-by-field checks miss:
+
+```java
+assertEquals("""
+        {
+          "status" : "up_to_date",
+          ...
+        }""".formatted(varPath, varUrl, DRIVER, ADAPTER),
+        client.readLineAsJson(15_000));
+```
diff --git 
a/cayenne-mcp-server/src/test/java/org/apache/cayenne/mcp/TestMcpClient.java 
b/cayenne-mcp-server/src/test/java/org/apache/cayenne/mcp/TestMcpClient.java
new file mode 100644
index 000000000..61ad21f52
--- /dev/null
+++ b/cayenne-mcp-server/src/test/java/org/apache/cayenne/mcp/TestMcpClient.java
@@ -0,0 +1,141 @@
+/*****************************************************************
+ *   Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    https://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ ****************************************************************/
+package org.apache.cayenne.mcp;
+
+import org.junit.jupiter.api.extension.AfterEachCallback;
+import org.junit.jupiter.api.extension.BeforeEachCallback;
+import org.junit.jupiter.api.extension.ExtensionContext;
+import tools.jackson.databind.JsonNode;
+import tools.jackson.databind.ObjectMapper;
+import tools.jackson.databind.json.JsonMapper;
+
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.OutputStreamWriter;
+import java.nio.file.Path;
+import java.util.concurrent.TimeUnit;
+
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * JUnit 5 extension that wraps the in-process MCP server lifecycle and 
provides methods
+ * for sending JSON-RPC requests, reading responses, and extracting 
tool-result payloads.
+ * Declare as an instance field annotated with {@code @RegisterExtension}.
+ */
+public class TestMcpClient implements BeforeEachCallback, AfterEachCallback {
+
+    private static final ObjectMapper MAPPER = JsonMapper.builder().build();
+
+    private TestMcpServer server;
+    private BufferedWriter writer;
+    private BufferedReader reader;
+
+    @Override
+    public void beforeEach(ExtensionContext context) {
+        server = TestMcpServer.start();
+        writer = new BufferedWriter(new 
OutputStreamWriter(server.getOutputStream()));
+        reader = new BufferedReader(new 
InputStreamReader(server.getInputStream()));
+
+        send("""
+                {"jsonrpc":"2.0","id":1,"method":"initialize","params":{\
+                "protocolVersion":"2024-11-05",\
+                "capabilities":{},\
+                "clientInfo":{"name":"test","version":"1.0"}}}""");
+        String initResponse = readLine(5_000);
+        assertTrue(initResponse.contains("\"id\":1"), "initialize response 
missing: " + initResponse);
+        send("""
+                
{"jsonrpc":"2.0","method":"notifications/initialized","params":{}}""");
+    }
+
+    @Override
+    public void afterEach(ExtensionContext context) {
+        try {
+            writer.close();
+            assertTrue(server.waitFor(10, TimeUnit.SECONDS), "Server thread 
did not stop after stdin was closed");
+        } catch (InterruptedException e) {
+            Thread.currentThread().interrupt();
+            throw new RuntimeException("Interrupted while waiting for server 
to stop", e);
+        } catch (IOException e) {
+            throw new RuntimeException("Failed to close client", e);
+        }
+    }
+
+    /**
+     * Sends a JSON-RPC message to the server.
+     */
+    public void send(String json) {
+        try {
+            writer.write(json);
+            writer.newLine();
+            writer.flush();
+        } catch (IOException e) {
+            throw new RuntimeException("Failed to send message", e);
+        }
+    }
+
+    /**
+     * Reads the next non-blank response line, blocking up to {@code 
timeoutMs} milliseconds.
+     */
+    public String readLine(long timeoutMs) {
+        long deadline = System.currentTimeMillis() + timeoutMs;
+        try {
+            while (System.currentTimeMillis() < deadline) {
+                if (reader.ready()) {
+                    String line = reader.readLine();
+                    if (line != null && !line.isBlank()) {
+                        return line;
+                    }
+                }
+                Thread.sleep(20);
+            }
+        } catch (InterruptedException e) {
+            Thread.currentThread().interrupt();
+            throw new RuntimeException("Interrupted while waiting for 
response", e);
+        } catch (IOException e) {
+            throw new RuntimeException("Failed to read response", e);
+        }
+        throw new AssertionError("No response within " + timeoutMs + "ms");
+    }
+
+    /**
+     * Reads the next response line and returns the pretty-printed tool result 
JSON payload.
+     */
+    public String readLineAsJson(long timeoutMs) {
+        String mcpResponse = readLine(timeoutMs);
+        JsonNode root = MAPPER.readTree(mcpResponse);
+        String text = root.at("/result/content/0/text").asString();
+        JsonNode payload = MAPPER.readTree(text);
+        return 
MAPPER.writerWithDefaultPrettyPrinter().writeValueAsString(payload)
+                .replace("\r\n", "\n");
+    }
+
+    /**
+     * Sends a {@code tools/call} JSON-RPC request for a tool that accepts
+     * {@code projectPath} and {@code dataMap} arguments.
+     */
+    public void sendToolCall(int id, String toolName, Path project, String 
dataMap) {
+        String escapedPath = project.toString().replace("\\", "\\\\");
+        send("""
+                {"jsonrpc":"2.0","id":%d,"method":"tools/call","params":\
+                {"name":"%s","arguments":\
+                {"projectPath":"%s","dataMap":"%s"}}}""".formatted(id, 
toolName, escapedPath, dataMap));
+    }
+}
diff --git 
a/cayenne-mcp-server/src/test/java/org/apache/cayenne/mcp/InProcessMcpServer.java
 b/cayenne-mcp-server/src/test/java/org/apache/cayenne/mcp/TestMcpServer.java
similarity index 88%
rename from 
cayenne-mcp-server/src/test/java/org/apache/cayenne/mcp/InProcessMcpServer.java
rename to 
cayenne-mcp-server/src/test/java/org/apache/cayenne/mcp/TestMcpServer.java
index 2fd346ca6..cb8834cbb 100644
--- 
a/cayenne-mcp-server/src/test/java/org/apache/cayenne/mcp/InProcessMcpServer.java
+++ b/cayenne-mcp-server/src/test/java/org/apache/cayenne/mcp/TestMcpServer.java
@@ -10,13 +10,13 @@ import java.util.concurrent.TimeUnit;
 /**
  * Handles the lifecycle of a test in-process MCP server.
  */
-public class InProcessMcpServer {
+public class TestMcpServer {
 
     private final OutputStream outputStream;
     private final InputStream inputStream;
     private final Thread serverThread;
 
-    public static InProcessMcpServer start() {
+    public static TestMcpServer start() {
         try {
             PipedOutputStream clientOut = new PipedOutputStream();
             PipedInputStream serverIn = new PipedInputStream(clientOut);
@@ -29,13 +29,13 @@ public class InProcessMcpServer {
             serverThread.setDaemon(true);
             serverThread.start();
 
-            return new InProcessMcpServer(clientOut, clientIn, serverThread);
+            return new TestMcpServer(clientOut, clientIn, serverThread);
         } catch (IOException e) {
             throw new RuntimeException("Failed to start in-process MCP 
server", e);
         }
     }
 
-    private InProcessMcpServer(OutputStream outputStream, InputStream 
inputStream, Thread serverThread) {
+    private TestMcpServer(OutputStream outputStream, InputStream inputStream, 
Thread serverThread) {
         this.outputStream = outputStream;
         this.inputStream = inputStream;
         this.serverThread = serverThread;
diff --git 
a/cayenne-mcp-server/src/test/java/org/apache/cayenne/mcp/tools/cgen/CgenRunMcpIT.java
 
b/cayenne-mcp-server/src/test/java/org/apache/cayenne/mcp/tools/cgen/CgenRunMcpIT.java
index 898ac3319..bbe80cecf 100644
--- 
a/cayenne-mcp-server/src/test/java/org/apache/cayenne/mcp/tools/cgen/CgenRunMcpIT.java
+++ 
b/cayenne-mcp-server/src/test/java/org/apache/cayenne/mcp/tools/cgen/CgenRunMcpIT.java
@@ -18,23 +18,15 @@
  ****************************************************************/
 package org.apache.cayenne.mcp.tools.cgen;
 
-import org.apache.cayenne.mcp.InProcessMcpServer;
-import org.junit.jupiter.api.AfterEach;
+import org.apache.cayenne.mcp.TestMcpClient;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.RegisterExtension;
 import org.junit.jupiter.api.io.TempDir;
-import tools.jackson.databind.JsonNode;
-import tools.jackson.databind.ObjectMapper;
-import tools.jackson.databind.json.JsonMapper;
 
-import java.io.BufferedReader;
-import java.io.BufferedWriter;
 import java.io.IOException;
-import java.io.InputStreamReader;
-import java.io.OutputStreamWriter;
 import java.nio.file.Files;
 import java.nio.file.Path;
-import java.util.concurrent.TimeUnit;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertTrue;
@@ -46,57 +38,34 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
  */
 public class CgenRunMcpIT {
 
-    private static final ObjectMapper MAPPER = JsonMapper.builder().build();
+    @RegisterExtension
+    TestMcpClient client = new TestMcpClient();
 
     @TempDir
     Path tempDir;
 
-    private InProcessMcpServer server;
-    private BufferedWriter writer;
-    private BufferedReader reader;
     private Path projectFile;
     private Path destDir;
 
     @BeforeEach
-    void setUp() throws Exception {
+    void setUp() throws IOException {
         destDir = tempDir.resolve("generated");
         writeFixture("PersonMap", "com.example", destDir, true);
         projectFile = tempDir.resolve("cayenne-project.xml");
-
-        server = InProcessMcpServer.start();
-        writer = new BufferedWriter(new 
OutputStreamWriter(server.getOutputStream()));
-        reader = new BufferedReader(new 
InputStreamReader(server.getInputStream()));
-
-        send("""
-                {"jsonrpc":"2.0","id":1,"method":"initialize","params":{\
-                "protocolVersion":"2024-11-05",\
-                "capabilities":{},\
-                "clientInfo":{"name":"test","version":"1.0"}}}""");
-        String initResponse = readLine(5_000);
-        assertTrue(initResponse.contains("\"id\":1"), "initialize response 
missing: " + initResponse);
-
-        send("""
-                
{"jsonrpc":"2.0","method":"notifications/initialized","params":{}}""");
-    }
-
-    @AfterEach
-    void stopServer() throws Exception {
-        writer.close();
-        assertTrue(server.waitFor(10, TimeUnit.SECONDS), "Server thread did 
not stop after stdin was closed");
     }
 
     @Test
-    public void toolsListIncludesCgenRun() throws Exception {
-        send("""
+    public void toolsListIncludesCgenRun() {
+        client.send("""
                 {"jsonrpc":"2.0","id":2,"method":"tools/list","params":{}}""");
 
-        String response = readLine(5_000);
+        String response = client.readLine(5_000);
         assertTrue(response.contains("\"cgen_run\""), "tools/list missing 
'cgen_run': " + response);
     }
 
     @Test
-    public void projectNotFoundReturnsValidationError() throws Exception {
-        send("""
+    public void projectNotFoundReturnsValidationError() {
+        client.send("""
                 {"jsonrpc":"2.0","id":3,"method":"tools/call","params":{\
                 "name":"cgen_run","arguments":{\
                 "projectPath":"/no/such/file.xml","dataMap":"X"}}}""");
@@ -122,12 +91,12 @@ public class CgenRunMcpIT {
                     "code" : "project_not_found",
                     "message" : "No readable file at /no/such/file.xml"
                   }
-                }""", extractPayload(readLine(5_000)));
+                }""", client.readLineAsJson(5_000));
     }
 
     @Test
-    public void generatesFilesOnFirstRun() throws Exception {
-        send(callJson(4, projectFile, "PersonMap"));
+    public void generatesFilesOnFirstRun() {
+        client.sendToolCall(4, "cgen_run", projectFile, "PersonMap");
 
         // JSON-escape backslashes so Windows paths round-trip correctly 
through Jackson serialization
         String superPath = 
destDir.resolve("com/example/auto/_Person.java").toAbsolutePath().toString().replace("\\",
 "\\\\");
@@ -162,15 +131,15 @@ public class CgenRunMcpIT {
                     "destDirWritable" : true
                   },
                   "error" : null
-                }""".formatted(superPath, subPath, destPath), 
extractPayload(readLine(15_000)));
+                }""".formatted(superPath, subPath, destPath), 
client.readLineAsJson(15_000));
     }
 
     @Test
-    public void upToDateOnSecondRun() throws Exception {
-        send(callJson(5, projectFile, "PersonMap"));
-        readLine(15_000);
+    public void upToDateOnSecondRun() {
+        client.sendToolCall(5, "cgen_run", projectFile, "PersonMap");
+        client.readLine(15_000);
 
-        send(callJson(6, projectFile, "PersonMap"));
+        client.sendToolCall(6, "cgen_run", projectFile, "PersonMap");
 
         String destPath = destDir.toAbsolutePath().toString().replace("\\", 
"\\\\");
 
@@ -194,46 +163,7 @@ public class CgenRunMcpIT {
                     "destDirWritable" : true
                   },
                   "error" : null
-                }""".formatted(destPath), extractPayload(readLine(15_000)));
-    }
-    
-    private String extractPayload(String mcpResponse) throws Exception {
-        JsonNode root = MAPPER.readTree(mcpResponse);
-        String text = root.at("/result/content/0/text").asText();
-        JsonNode payload = MAPPER.readTree(text);
-        // Normalize CRLF → LF: Jackson's DefaultPrettyPrinter uses 
System.lineSeparator()
-        // which is \r\n on Windows, but Java text blocks always use \n.
-        return 
MAPPER.writerWithDefaultPrettyPrinter().writeValueAsString(payload)
-                .replace("\r\n", "\n");
-    }
-
-    private String callJson(int id, Path project, String dataMap) {
-        String escapedPath = project.toString().replace("\\", "\\\\");
-        return String.format(
-                
"{\"jsonrpc\":\"2.0\",\"id\":%d,\"method\":\"tools/call\",\"params\":{" +
-                "\"name\":\"cgen_run\",\"arguments\":{" +
-                "\"projectPath\":\"%s\",\"dataMap\":\"%s\"}}}",
-                id, escapedPath, dataMap);
-    }
-
-    private void send(String json) throws IOException {
-        writer.write(json);
-        writer.newLine();
-        writer.flush();
-    }
-
-    private String readLine(long timeoutMs) throws Exception {
-        long deadline = System.currentTimeMillis() + timeoutMs;
-        while (System.currentTimeMillis() < deadline) {
-            if (reader.ready()) {
-                String line = reader.readLine();
-                if (line != null && !line.isBlank()) {
-                    return line;
-                }
-            }
-            Thread.sleep(20);
-        }
-        throw new AssertionError("No response within " + timeoutMs + "ms");
+                }""".formatted(destPath), client.readLineAsJson(15_000));
     }
 
     private void writeFixture(String mapName, String pkg, Path destDir, 
boolean makePairs) throws IOException {
diff --git 
a/cayenne-mcp-server/src/test/java/org/apache/cayenne/mcp/tools/dbimport/DbImportRunMcpIT.java
 
b/cayenne-mcp-server/src/test/java/org/apache/cayenne/mcp/tools/dbimport/DbImportRunMcpIT.java
new file mode 100644
index 000000000..10ffa610e
--- /dev/null
+++ 
b/cayenne-mcp-server/src/test/java/org/apache/cayenne/mcp/tools/dbimport/DbImportRunMcpIT.java
@@ -0,0 +1,237 @@
+/*****************************************************************
+ *   Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    https://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ ****************************************************************/
+package org.apache.cayenne.mcp.tools.dbimport;
+
+import org.apache.cayenne.dba.hsqldb.HSQLDBNoSchemaAdapter;
+import org.apache.cayenne.mcp.TestMcpClient;
+import org.apache.cayenne.modeler.pref.PreferenceNodeIds;
+import org.apache.cayenne.modeler.pref.PrefsLocator;
+import org.apache.cayenne.modeler.pref.adapters.DataMapPrefs;
+import org.apache.cayenne.modeler.pref.dbconnector.DBConnector;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.RegisterExtension;
+import org.junit.jupiter.api.io.TempDir;
+
+import java.nio.file.Path;
+import java.sql.Connection;
+import java.sql.DriverManager;
+import java.sql.Statement;
+import java.util.UUID;
+import java.util.prefs.Preferences;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * Integration tests for the {@code dbimport_run} tool over the MCP stdio 
protocol.
+ * Complements {@link DbImportRunIT}, which calls the tool directly; this 
class exercises
+ * the full JSON-RPC round-trip through the in-process MCP server.
+ */
+public class DbImportRunMcpIT {
+
+    private static final String HSQL_ADAPTER = 
HSQLDBNoSchemaAdapter.class.getName();
+    private static final String HSQL_DRIVER = "org.hsqldb.jdbc.JDBCDriver";
+
+    @RegisterExtension
+    TestMcpClient client = new TestMcpClient();
+
+    @TempDir
+    Path tempDir;
+
+    private String dbUrl;
+    private Connection dbConn;
+    private Path projectFile;
+    private Path dataMapFile;
+    private Preferences dataMapPrefsNode;
+
+    @BeforeEach
+    void setUp() throws Exception {
+        dbUrl = "jdbc:hsqldb:mem:dbimport_mcp_%s;shutdown=true"
+                .formatted(UUID.randomUUID().toString().replace("-", ""));
+        dbConn = DriverManager.getConnection(dbUrl, "SA", "");
+        try (Statement s = dbConn.createStatement()) {
+            s.execute("""
+                    CREATE TABLE artist (
+                        id   INTEGER NOT NULL PRIMARY KEY,
+                        name VARCHAR(255)
+                    )""");
+            s.execute("""
+                    CREATE TABLE painting (
+                        id        INTEGER NOT NULL PRIMARY KEY,
+                        title     VARCHAR(255),
+                        artist_id INTEGER,
+                        FOREIGN KEY (artist_id) REFERENCES artist(id)
+                    )""");
+        }
+
+        projectFile = DbImportRunValidationTest.copyFixture("with-dbimport", 
tempDir);
+        dataMapFile = tempDir.resolve("TestMap.map.xml");
+
+        // The in-process MCP server uses new PrefsLocator() → 
Preferences.userRoot().
+        // Write the connector there so the server can find it when the tool 
runs.
+        String dataMapId = 
PreferenceNodeIds.idForPath(dataMapFile.toAbsolutePath().toString());
+        PrefsLocator locator = new PrefsLocator(Preferences.userRoot());
+        dataMapPrefsNode = locator.dataMapNode(dataMapId);
+        DBConnector connector = new DBConnector();
+        connector.setUrl(dbUrl);
+        connector.setUserName("SA");
+        connector.setPassword("");
+        connector.setJdbcDriver(HSQL_DRIVER);
+        connector.setDbAdapter(HSQL_ADAPTER);
+        new DataMapPrefs(dataMapPrefsNode).setConnector(connector);
+    }
+
+    @AfterEach
+    void tearDown() throws Exception {
+        if (dataMapPrefsNode != null) {
+            try {
+                dataMapPrefsNode.removeNode();
+            } catch (Exception ignored) {
+            }
+        }
+        if (dbConn != null && !dbConn.isClosed()) {
+            dbConn.close();
+        }
+    }
+
+    @Test
+    public void toolsListIncludesDbImportRun() {
+        client.send("""
+                {"jsonrpc":"2.0","id":2,"method":"tools/list","params":{}}""");
+
+        String response = client.readLine(5_000);
+        assertTrue(response.contains("\"dbimport_run\""), "tools/list missing 
'dbimport_run': " + response);
+    }
+
+    @Test
+    public void projectNotFoundReturnsValidationError() {
+        client.send("""
+                {"jsonrpc":"2.0","id":3,"method":"tools/call","params":{\
+                "name":"dbimport_run","arguments":{\
+                "projectPath":"/no/such/file.xml","dataMap":"TestMap"}}}""");
+
+        assertEquals("""
+                {
+                  "status" : "validation_failed",
+                  "summary" : {
+                    "tokensConsidered" : 0,
+                    "tokensApplied" : 0,
+                    "entitiesAdded" : 0,
+                    "entitiesRemoved" : 0,
+                    "entitiesModified" : 0,
+                    "relationshipsAdded" : 0
+                  },
+                  "resolved" : null,
+                  "warnings" : [ ],
+                  "validation" : {
+                    "projectFound" : false,
+                    "dataMapFound" : null,
+                    "reverseEngineeringConfigPresent" : null,
+                    "dbConnectorPresent" : null,
+                    "jdbcDriverLoadable" : null,
+                    "jdbcConnectionOpened" : null
+                  },
+                  "error" : {
+                    "code" : "project_not_found",
+                    "message" : "No readable file at /no/such/file.xml"
+                  }
+                }""", client.readLineAsJson(5_000));
+    }
+
+    @Test
+    public void happyPathImportsEntities() {
+        client.sendToolCall(4, "dbimport_run", projectFile, "TestMap");
+
+        String dataMapPath = 
dataMapFile.toAbsolutePath().toString().replace("\\", "\\\\");
+        String escapedDbUrl = dbUrl.replace("\\", "\\\\");
+
+        assertEquals("""
+                {
+                  "status" : "imported",
+                  "summary" : {
+                    "tokensConsidered" : 2,
+                    "tokensApplied" : 2,
+                    "entitiesAdded" : 2,
+                    "entitiesRemoved" : 0,
+                    "entitiesModified" : 0,
+                    "relationshipsAdded" : 0
+                  },
+                  "resolved" : {
+                    "dataMapFile" : "%s",
+                    "jdbcUrl" : "%s",
+                    "jdbcDriver" : "%s",
+                    "dbAdapter" : "%s"
+                  },
+                  "warnings" : [ "Can't find ObjEntity for PAINTING", "Db 
Relationship (Db Relationship : toMany (ARTIST.ID, PAINTING.ARTIST_ID)) will 
have GUESSED Obj Relationship reflection. " ],
+                  "validation" : {
+                    "projectFound" : true,
+                    "dataMapFound" : true,
+                    "reverseEngineeringConfigPresent" : true,
+                    "dbConnectorPresent" : true,
+                    "jdbcDriverLoadable" : true,
+                    "jdbcConnectionOpened" : true
+                  },
+                  "error" : null
+                }""".formatted(dataMapPath, escapedDbUrl, HSQL_DRIVER, 
HSQL_ADAPTER),
+                client.readLineAsJson(30_000));
+    }
+
+    @Test
+    public void upToDateOnSecondRun() {
+        client.sendToolCall(5, "dbimport_run", projectFile, "TestMap");
+        client.readLine(30_000);
+
+        client.sendToolCall(6, "dbimport_run", projectFile, "TestMap");
+
+        String dataMapPath = 
dataMapFile.toAbsolutePath().toString().replace("\\", "\\\\");
+        String escapedDbUrl = dbUrl.replace("\\", "\\\\");
+
+        assertEquals("""
+                {
+                  "status" : "up_to_date",
+                  "summary" : {
+                    "tokensConsidered" : 0,
+                    "tokensApplied" : 0,
+                    "entitiesAdded" : 0,
+                    "entitiesRemoved" : 0,
+                    "entitiesModified" : 0,
+                    "relationshipsAdded" : 0
+                  },
+                  "resolved" : {
+                    "dataMapFile" : "%s",
+                    "jdbcUrl" : "%s",
+                    "jdbcDriver" : "%s",
+                    "dbAdapter" : "%s"
+                  },
+                  "warnings" : [ ],
+                  "validation" : {
+                    "projectFound" : true,
+                    "dataMapFound" : true,
+                    "reverseEngineeringConfigPresent" : true,
+                    "dbConnectorPresent" : true,
+                    "jdbcDriverLoadable" : true,
+                    "jdbcConnectionOpened" : true
+                  },
+                  "error" : null
+                }""".formatted(dataMapPath, escapedDbUrl, HSQL_DRIVER, 
HSQL_ADAPTER),
+                client.readLineAsJson(30_000));
+    }
+}

Reply via email to