Reuse npm cache for run previews

This commit is contained in:
王鹏
2026-06-18 12:53:00 +08:00
parent d34de73eba
commit 7e00b65159
2 changed files with 1756 additions and 0 deletions

View File

@@ -0,0 +1,623 @@
package com.ruoyi.generator.service.front;
import com.ruoyi.generator.domain.front.FrontProject;
import com.ruoyi.generator.domain.front.dto.ProjectRunPreviewStatus;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.reflect.Field;
import java.net.InetSocketAddress;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpHandler;
import com.sun.net.httpserver.HttpServer;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.mockito.Mockito.times;
public class FrontProjectRunPreviewServiceImplTest
{
@Rule
public TemporaryFolder temporaryFolder = new TemporaryFolder();
private FrontProjectRunPreviewServiceImpl runPreviewService;
@Mock
private IFrontProjectService frontProjectService;
@Mock
private IFrontProjectPreviewService frontProjectPreviewService;
@Mock
private RunPreviewPortAllocator portAllocator;
@Mock
private RunPreviewDatabaseInitializer databaseInitializer;
@Mock
private RunPreviewProcessRunner processRunner;
private RecordingProcessKiller processKiller;
private FakeProcess backendProcess;
private FakeProcess frontendProcess;
private FakeProcess adminFrontendProcess;
@Before
public void setUp() throws Exception
{
MockitoAnnotations.initMocks(this);
runPreviewService = new FrontProjectRunPreviewServiceImpl();
processKiller = new RecordingProcessKiller();
backendProcess = new FakeProcess();
frontendProcess = new FakeProcess();
adminFrontendProcess = new FakeProcess();
setField("frontProjectService", frontProjectService);
setField("frontProjectPreviewService", frontProjectPreviewService);
setField("portAllocator", portAllocator);
setField("databaseInitializer", databaseInitializer);
setField("processRunner", processRunner);
setField("processKiller", processKiller);
setField("workspaceRoot", temporaryFolder.newFolder("preview-workspaces").getAbsolutePath());
setField("mysqlHost", "127.0.0.1");
setField("mysqlPort", 3306);
setField("mysqlUsername", "root");
setField("mysqlPassword", "123456");
}
@Test
public void startCreatesWorkspaceInitializesDatabaseAndStartsProcesses() throws Exception
{
when(frontProjectService.getProject(10L, 20L)).thenReturn(project());
when(frontProjectPreviewService.downloadAll(10L, 20L)).thenReturn(zipWithGeneratedProject());
when(portAllocator.allocate()).thenReturn(18080, 18081);
when(databaseInitializer.initialize(any(File.class), anyString(), eq("127.0.0.1"), eq(3306), eq("root"), eq("123456")))
.thenReturn("jdbc:mysql://127.0.0.1:3306/preview_20_test");
whenBackendStarts();
when(processRunner.startFrontend(any(File.class), any(Map.class))).thenReturn(frontendProcess);
ProjectRunPreviewStatus status = runPreviewService.start(10L, 20L);
assertEquals("STARTING", status.getStatus());
assertEquals("http://127.0.0.1:18080", status.getBackendUrl());
assertEquals("http://127.0.0.1:18081", status.getFrontendUrl());
assertTrue(new File(status.getWorkspacePath(), "pom.xml").isFile());
assertTrue(new File(status.getWorkspacePath(), "vue/package.json").isFile());
ArgumentCaptor<File> sqlDirectoryCaptor = ArgumentCaptor.forClass(File.class);
ArgumentCaptor<String> databaseNameCaptor = ArgumentCaptor.forClass(String.class);
verify(databaseInitializer).initialize(sqlDirectoryCaptor.capture(), databaseNameCaptor.capture(), eq("127.0.0.1"), eq(3306), eq("root"), eq("123456"));
assertEquals("sql", sqlDirectoryCaptor.getValue().getName());
assertTrue(databaseNameCaptor.getValue().startsWith("preview_20_"));
ArgumentCaptor<Map> backendEnvCaptor = ArgumentCaptor.forClass(Map.class);
verify(processRunner).startBackend(any(File.class), any(File.class), any(File.class), anyString(), any(File.class), backendEnvCaptor.capture());
assertEquals("18080", backendEnvCaptor.getValue().get("SERVER_PORT"));
assertEquals("jdbc:mysql://127.0.0.1:3306/preview_20_test", backendEnvCaptor.getValue().get("DB_URL"));
assertEquals("root", backendEnvCaptor.getValue().get("DB_USERNAME"));
assertEquals("123456", backendEnvCaptor.getValue().get("DB_PASSWORD"));
assertTrue(((String) backendEnvCaptor.getValue().get("JAVA_HOME")).length() > 0);
assertTrue(((String) backendEnvCaptor.getValue().get("MAVEN_OPTS")).contains("java.io.tmpdir"));
assertTrue(((String) backendEnvCaptor.getValue().get("MAVEN_OPTS")).contains("java-tmp"));
assertTrue(((String) backendEnvCaptor.getValue().get("JAVA_TOOL_OPTIONS")).contains("java.io.tmpdir"));
ArgumentCaptor<Map> frontendEnvCaptor = ArgumentCaptor.forClass(Map.class);
verify(processRunner).startFrontend(any(File.class), frontendEnvCaptor.capture());
assertEquals("18081", frontendEnvCaptor.getValue().get("PORT"));
assertEquals("http://127.0.0.1:18080", frontendEnvCaptor.getValue().get("VUE_APP_API_BASE_URL"));
String frontendCache = (String) frontendEnvCaptor.getValue().get("NPM_CONFIG_CACHE");
assertEquals(".npm-cache", new File(frontendCache).getName());
assertEquals(new File(status.getWorkspacePath()).getParentFile().getCanonicalFile(),
new File(frontendCache).getParentFile().getCanonicalFile());
assertEquals(frontendCache, frontendEnvCaptor.getValue().get("npm_config_cache"));
assertEquals("false", frontendEnvCaptor.getValue().get("NPM_CONFIG_UPDATE_NOTIFIER"));
assertEquals("1", frontendEnvCaptor.getValue().get("NO_UPDATE_NOTIFIER"));
assertEquals("false", frontendEnvCaptor.getValue().get("NPM_CONFIG_OFFLINE"));
assertEquals("true", frontendEnvCaptor.getValue().get("NPM_CONFIG_PREFER_OFFLINE"));
assertEquals("false", frontendEnvCaptor.getValue().get("NPM_CONFIG_PREFER_ONLINE"));
assertEquals("", frontendEnvCaptor.getValue().get("NPM_CONFIG_PROXY"));
assertEquals("", frontendEnvCaptor.getValue().get("NPM_CONFIG_HTTPS_PROXY"));
assertEquals("*", frontendEnvCaptor.getValue().get("NO_PROXY"));
}
@Test
public void startRenamesGeneratedJavaFileToMatchPublicApplicationClass() throws Exception
{
when(frontProjectService.getProject(10L, 20L)).thenReturn(project());
when(frontProjectPreviewService.downloadAll(10L, 20L)).thenReturn(zipWithMismatchedApplicationFile());
when(portAllocator.allocate()).thenReturn(18080, 18081);
when(databaseInitializer.initialize(any(File.class), anyString(), anyString(), anyInt(), anyString(), anyString()))
.thenReturn("jdbc:mysql://127.0.0.1:3306/preview_20_test");
whenBackendStarts();
when(processRunner.startFrontend(any(File.class), any(Map.class))).thenReturn(frontendProcess);
ProjectRunPreviewStatus status = runPreviewService.start(10L, 20L);
File javaDirectory = new File(status.getWorkspacePath(), "demo-backend/src/main/java/com/example/demo");
assertTrue(new File(javaDirectory, "DemoApplication.java").isFile());
assertTrue(!new File(javaDirectory, "Application.java").exists());
}
@Test
public void startSupportsCombinedDownloadPackageLayout() throws Exception
{
when(frontProjectService.getProject(10L, 20L)).thenReturn(project());
when(frontProjectPreviewService.downloadAll(10L, 20L)).thenReturn(zipWithCombinedGeneratedProject());
when(portAllocator.allocate()).thenReturn(18080, 18081, 18082);
when(databaseInitializer.initialize(any(File.class), anyString(), eq("127.0.0.1"), eq(3306), eq("root"), eq("123456")))
.thenReturn("jdbc:mysql://127.0.0.1:3306/preview_20_test");
whenBackendStarts();
when(processRunner.startFrontend(any(File.class), any(Map.class))).thenReturn(frontendProcess, adminFrontendProcess);
ProjectRunPreviewStatus status = runPreviewService.start(10L, 20L);
assertEquals("STARTING", status.getStatus());
assertEquals("http://127.0.0.1:18081", status.getFrontendUrl());
assertEquals("http://127.0.0.1:18082", status.getAdminFrontendUrl());
assertTrue(new File(status.getWorkspacePath(), "demo-backend/pom.xml").isFile());
assertTrue(new File(status.getWorkspacePath(), "demo-web/package.json").isFile());
ArgumentCaptor<File> previewBuildDirectoryCaptor = ArgumentCaptor.forClass(File.class);
ArgumentCaptor<File> rootPomCaptor = ArgumentCaptor.forClass(File.class);
ArgumentCaptor<File> reactorPomCaptor = ArgumentCaptor.forClass(File.class);
ArgumentCaptor<String> backendModulePathCaptor = ArgumentCaptor.forClass(String.class);
ArgumentCaptor<File> localRepositoryCaptor = ArgumentCaptor.forClass(File.class);
verify(processRunner).startBackend(previewBuildDirectoryCaptor.capture(), rootPomCaptor.capture(), reactorPomCaptor.capture(), backendModulePathCaptor.capture(), localRepositoryCaptor.capture(), any(Map.class));
assertEquals(".easycode-preview", previewBuildDirectoryCaptor.getValue().getName());
assertEquals("pom.xml", rootPomCaptor.getValue().getName());
assertEquals("pom.xml", reactorPomCaptor.getValue().getName());
assertEquals("../demo-backend", backendModulePathCaptor.getValue());
assertEquals("repository", localRepositoryCaptor.getValue().getName());
String reactorPom = new String(Files.readAllBytes(reactorPomCaptor.getValue().toPath()), StandardCharsets.UTF_8);
assertTrue(reactorPom.contains("ruoyi-common"));
assertTrue(reactorPom.contains("ruoyi-system"));
assertTrue(reactorPom.contains("ruoyi-framework"));
assertTrue(reactorPom.contains("<module>../demo-backend</module>"));
ArgumentCaptor<File> frontendDirectoryCaptor = ArgumentCaptor.forClass(File.class);
ArgumentCaptor<Map> frontendEnvCaptor = ArgumentCaptor.forClass(Map.class);
verify(processRunner, times(2)).startFrontend(frontendDirectoryCaptor.capture(), frontendEnvCaptor.capture());
assertEquals("demo-web", frontendDirectoryCaptor.getAllValues().get(0).getName());
assertEquals("demo-admin", frontendDirectoryCaptor.getAllValues().get(1).getName());
String frontendCache = (String) frontendEnvCaptor.getAllValues().get(0).get("NPM_CONFIG_CACHE");
String adminFrontendCache = (String) frontendEnvCaptor.getAllValues().get(1).get("NPM_CONFIG_CACHE");
assertEquals(frontendCache, adminFrontendCache);
assertEquals(".npm-cache", new File(frontendCache).getName());
}
@Test
public void startInitializesDatabaseFromBackendSqlWhenCombinedPackageHasRootSql() throws Exception
{
when(frontProjectService.getProject(10L, 20L)).thenReturn(project());
when(frontProjectPreviewService.downloadAll(10L, 20L)).thenReturn(zipWithCombinedGeneratedProjectAndBackendSql());
when(portAllocator.allocate()).thenReturn(18080, 18081);
when(databaseInitializer.initialize(any(File.class), anyString(), eq("127.0.0.1"), eq(3306), eq("root"), eq("123456")))
.thenReturn("jdbc:mysql://127.0.0.1:3306/preview_20_test");
whenBackendStarts();
when(processRunner.startFrontend(any(File.class), any(Map.class))).thenReturn(frontendProcess);
ProjectRunPreviewStatus status = runPreviewService.start(10L, 20L);
ArgumentCaptor<File> sqlDirectoryCaptor = ArgumentCaptor.forClass(File.class);
verify(databaseInitializer).initialize(sqlDirectoryCaptor.capture(), anyString(), eq("127.0.0.1"), eq(3306), eq("root"), eq("123456"));
assertEquals("STARTING", status.getStatus());
assertEquals("sql", sqlDirectoryCaptor.getValue().getName());
assertEquals("demo-backend", sqlDirectoryCaptor.getValue().getParentFile().getName());
}
@Test
public void startWrapsGeneratedVueConfigForPreviewPorts() throws Exception
{
when(frontProjectService.getProject(10L, 20L)).thenReturn(project());
when(frontProjectPreviewService.downloadAll(10L, 20L)).thenReturn(zipWithCombinedGeneratedProject());
when(portAllocator.allocate()).thenReturn(18080, 18081);
when(databaseInitializer.initialize(any(File.class), anyString(), eq("127.0.0.1"), eq(3306), eq("root"), eq("123456")))
.thenReturn("jdbc:mysql://127.0.0.1:3306/preview_20_test");
whenBackendStarts();
when(processRunner.startFrontend(any(File.class), any(Map.class))).thenReturn(frontendProcess);
ProjectRunPreviewStatus status = runPreviewService.start(10L, 20L);
File frontendDirectory = new File(status.getWorkspacePath(), "demo-web");
assertTrue(new File(frontendDirectory, "vue.config.generated.js").isFile());
String previewConfig = new String(Files.readAllBytes(new File(frontendDirectory, "vue.config.js").toPath()), StandardCharsets.UTF_8);
assertTrue(previewConfig.contains("process.env.PORT"));
assertTrue(previewConfig.contains("process.env.VUE_APP_API_BASE_URL"));
assertTrue(previewConfig.contains("vue.config.generated.js"));
}
@Test
public void startWritesViteConfigForGeneratedViteFrontend() throws Exception
{
when(frontProjectService.getProject(10L, 20L)).thenReturn(project());
when(frontProjectPreviewService.downloadAll(10L, 20L)).thenReturn(zipWithViteGeneratedProject());
when(portAllocator.allocate()).thenReturn(18080, 18081);
when(databaseInitializer.initialize(any(File.class), anyString(), eq("127.0.0.1"), eq(3306), eq("root"), eq("123456")))
.thenReturn("jdbc:mysql://127.0.0.1:3306/preview_20_test");
whenBackendStarts();
when(processRunner.startFrontend(any(File.class), any(Map.class))).thenReturn(frontendProcess);
ProjectRunPreviewStatus status = runPreviewService.start(10L, 20L);
File frontendDirectory = new File(status.getWorkspacePath(), "demo-portal");
String viteConfig = new String(Files.readAllBytes(new File(frontendDirectory, "vite.config.js").toPath()), StandardCharsets.UTF_8);
assertTrue(viteConfig.contains("process.env.PORT"));
assertTrue(viteConfig.contains("process.env.VUE_APP_API_BASE_URL"));
assertTrue(viteConfig.contains("process.env.VITE_APP_API_BASE_URL"));
ArgumentCaptor<Map> frontendEnvCaptor = ArgumentCaptor.forClass(Map.class);
verify(processRunner).startFrontend(any(File.class), frontendEnvCaptor.capture());
assertEquals("/api", frontendEnvCaptor.getValue().get("VITE_APP_BASE_API"));
}
@Test
public void startUsesFreshWorkspaceWhenPreviousProjectWorkspaceExists() throws Exception
{
File legacyWorkspace = new File(temporaryFolder.getRoot(), "preview-workspaces/project-20");
assertTrue(legacyWorkspace.mkdirs());
File legacyFile = new File(legacyWorkspace, "server_code");
assertTrue(legacyFile.createNewFile());
when(frontProjectService.getProject(10L, 20L)).thenReturn(project());
when(frontProjectPreviewService.downloadAll(10L, 20L)).thenReturn(zipWithGeneratedProject());
when(portAllocator.allocate()).thenReturn(18080, 18081);
when(databaseInitializer.initialize(any(File.class), anyString(), anyString(), anyInt(), anyString(), anyString()))
.thenReturn("jdbc:mysql://127.0.0.1:3306/preview_20_test");
whenBackendStarts();
when(processRunner.startFrontend(any(File.class), any(Map.class))).thenReturn(frontendProcess);
ProjectRunPreviewStatus status = runPreviewService.start(10L, 20L);
assertEquals("STARTING", status.getStatus());
assertTrue("legacy workspace should be left alone when a new preview starts", legacyFile.isFile());
assertTrue("fresh workspace should include the project id and a unique suffix",
new File(status.getWorkspacePath()).getName().startsWith("project-20-"));
}
@Test
public void statusMarksPreviewRunningAfterFrontendServerIsReady() throws Exception
{
HttpServer frontendServer = HttpServer.create(new InetSocketAddress("127.0.0.1", 0), 0);
frontendServer.createContext("/", new HttpHandler()
{
@Override
public void handle(HttpExchange exchange) throws IOException
{
byte[] body = "ready".getBytes("UTF-8");
exchange.sendResponseHeaders(200, body.length);
OutputStream response = exchange.getResponseBody();
response.write(body);
response.close();
}
});
frontendServer.start();
try
{
when(frontProjectService.getProject(10L, 20L)).thenReturn(project());
when(frontProjectPreviewService.downloadAll(10L, 20L)).thenReturn(zipWithGeneratedProject());
when(portAllocator.allocate()).thenReturn(18080, frontendServer.getAddress().getPort());
when(databaseInitializer.initialize(any(File.class), anyString(), anyString(), anyInt(), anyString(), anyString()))
.thenReturn("jdbc:mysql://127.0.0.1:3306/preview_20_test");
whenBackendStarts();
when(processRunner.startFrontend(any(File.class), any(Map.class))).thenReturn(frontendProcess);
ProjectRunPreviewStatus startStatus = runPreviewService.start(10L, 20L);
ProjectRunPreviewStatus readyStatus = runPreviewService.status(10L, 20L);
assertEquals("STARTING", startStatus.getStatus());
assertEquals("RUNNING", readyStatus.getStatus());
}
finally
{
frontendServer.stop(0);
}
}
@Test
public void statusWaitsForBothFrontendsWhenAdminFrontendExists() throws Exception
{
HttpServer frontendServer = readyServer();
HttpServer adminFrontendServer = readyServer();
frontendServer.start();
try
{
when(frontProjectService.getProject(10L, 20L)).thenReturn(project());
when(frontProjectPreviewService.downloadAll(10L, 20L)).thenReturn(zipWithCombinedGeneratedProject());
when(portAllocator.allocate()).thenReturn(18080, frontendServer.getAddress().getPort(), adminFrontendServer.getAddress().getPort());
when(databaseInitializer.initialize(any(File.class), anyString(), anyString(), anyInt(), anyString(), anyString()))
.thenReturn("jdbc:mysql://127.0.0.1:3306/preview_20_test");
whenBackendStarts();
when(processRunner.startFrontend(any(File.class), any(Map.class))).thenReturn(frontendProcess, adminFrontendProcess);
runPreviewService.start(10L, 20L);
ProjectRunPreviewStatus waitingStatus = runPreviewService.status(10L, 20L);
adminFrontendServer.start();
ProjectRunPreviewStatus readyStatus = runPreviewService.status(10L, 20L);
assertEquals("STARTING", waitingStatus.getStatus());
assertEquals("RUNNING", readyStatus.getStatus());
}
finally
{
frontendServer.stop(0);
adminFrontendServer.stop(0);
}
}
@Test
public void statusUsesChineseMessageWhenGeneratedFrontendProcessExits() throws Exception
{
when(frontProjectService.getProject(10L, 20L)).thenReturn(project());
when(frontProjectPreviewService.downloadAll(10L, 20L)).thenReturn(zipWithGeneratedProject());
when(portAllocator.allocate()).thenReturn(18080, 18081);
when(databaseInitializer.initialize(any(File.class), anyString(), anyString(), anyInt(), anyString(), anyString()))
.thenReturn("jdbc:mysql://127.0.0.1:3306/preview_20_test");
whenBackendStarts();
when(processRunner.startFrontend(any(File.class), any(Map.class))).thenReturn(frontendProcess);
runPreviewService.start(10L, 20L);
frontendProcess.destroy();
ProjectRunPreviewStatus status = runPreviewService.status(10L, 20L);
assertEquals("FAILED", status.getStatus());
assertEquals("生成的前台前端进程已退出", status.getMessage());
}
@Test
public void stopDestroysRunningPreviewProcesses() throws Exception
{
when(frontProjectService.getProject(10L, 20L)).thenReturn(project());
when(frontProjectPreviewService.downloadAll(10L, 20L)).thenReturn(zipWithGeneratedProject());
when(portAllocator.allocate()).thenReturn(18080, 18081);
when(databaseInitializer.initialize(any(File.class), anyString(), anyString(), anyInt(), anyString(), anyString()))
.thenReturn("jdbc:mysql://127.0.0.1:3306/preview_20_test");
whenBackendStarts();
when(processRunner.startFrontend(any(File.class), any(Map.class))).thenReturn(frontendProcess);
runPreviewService.start(10L, 20L);
ProjectRunPreviewStatus status = runPreviewService.stop(10L, 20L);
assertEquals("STOPPED", status.getStatus());
assertTrue(backendProcess.destroyed);
assertTrue(frontendProcess.destroyed);
assertEquals(Integer.valueOf(18081), processKiller.stoppedPorts.get(0));
assertEquals(Integer.valueOf(18080), processKiller.stoppedPorts.get(1));
}
@Test
public void stopDestroysAdminFrontendAndAllListeningPorts() throws Exception
{
when(frontProjectService.getProject(10L, 20L)).thenReturn(project());
when(frontProjectPreviewService.downloadAll(10L, 20L)).thenReturn(zipWithCombinedGeneratedProject());
when(portAllocator.allocate()).thenReturn(18080, 18081, 18082);
when(databaseInitializer.initialize(any(File.class), anyString(), anyString(), anyInt(), anyString(), anyString()))
.thenReturn("jdbc:mysql://127.0.0.1:3306/preview_20_test");
whenBackendStarts();
when(processRunner.startFrontend(any(File.class), any(Map.class))).thenReturn(frontendProcess, adminFrontendProcess);
runPreviewService.start(10L, 20L);
ProjectRunPreviewStatus status = runPreviewService.stop(10L, 20L);
assertEquals("STOPPED", status.getStatus());
assertTrue(backendProcess.destroyed);
assertTrue(frontendProcess.destroyed);
assertTrue(adminFrontendProcess.destroyed);
assertEquals(Integer.valueOf(18082), processKiller.stoppedPorts.get(0));
assertEquals(Integer.valueOf(18081), processKiller.stoppedPorts.get(1));
assertEquals(Integer.valueOf(18080), processKiller.stoppedPorts.get(2));
}
@Test
public void statusReturnsNotStartedWhenNoSessionExists()
{
when(frontProjectService.getProject(10L, 20L)).thenReturn(project());
ProjectRunPreviewStatus status = runPreviewService.status(10L, 20L);
assertEquals("NOT_STARTED", status.getStatus());
assertEquals(Long.valueOf(20L), status.getProjectId());
}
private FrontProject project()
{
FrontProject project = new FrontProject();
project.setProjectId(20L);
project.setProjectName("Demo");
project.setProjectFileName("demo");
return project;
}
private void whenBackendStarts() throws IOException
{
when(processRunner.startBackend(any(File.class), any(File.class), any(File.class), anyString(), any(File.class), any(Map.class)))
.thenReturn(backendProcess);
}
private HttpServer readyServer() throws IOException
{
HttpServer server = HttpServer.create(new InetSocketAddress("127.0.0.1", 0), 0);
server.createContext("/", new HttpHandler()
{
@Override
public void handle(HttpExchange exchange) throws IOException
{
byte[] body = "ready".getBytes("UTF-8");
exchange.sendResponseHeaders(200, body.length);
OutputStream response = exchange.getResponseBody();
response.write(body);
response.close();
}
});
return server;
}
private byte[] zipWithGeneratedProject() throws IOException
{
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
ZipOutputStream zip = new ZipOutputStream(outputStream);
addEntry(zip, "pom.xml", "<project />");
addEntry(zip, "vue/package.json", "{\"scripts\":{\"dev\":\"vue-cli-service serve\"}}");
addEntry(zip, "sql/demo.sql", "create table demo (id bigint);");
zip.close();
return outputStream.toByteArray();
}
private byte[] zipWithMismatchedApplicationFile() throws IOException
{
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
ZipOutputStream zip = new ZipOutputStream(outputStream);
addEntry(zip, "demo-backend/pom.xml", "<project />");
addEntry(zip, "demo-backend/src/main/java/com/example/demo/Application.java",
"package com.example.demo;\n\npublic class DemoApplication {}\n");
addEntry(zip, "demo-web/package.json", "{\"scripts\":{\"dev\":\"vue-cli-service serve\"}}");
addEntry(zip, "sql/demo.sql", "create table demo (id bigint);");
zip.close();
return outputStream.toByteArray();
}
private byte[] zipWithCombinedGeneratedProject() throws IOException
{
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
ZipOutputStream zip = new ZipOutputStream(outputStream);
addEntry(zip, "demo-backend/pom.xml", "<project />");
addEntry(zip, "demo-web/package.json", "{\"scripts\":{\"dev\":\"vue-cli-service serve\"}}");
addEntry(zip, "demo-web/vue.config.js", "module.exports = { devServer: { port: 8082, proxy: { \"/api\": { target: \"http://localhost:8080\" } } } }");
addEntry(zip, "demo-admin/package.json", "{\"scripts\":{\"dev\":\"vue-cli-service serve\"}}");
addEntry(zip, "sql/demo.sql", "create table demo (id bigint);");
zip.close();
return outputStream.toByteArray();
}
private byte[] zipWithCombinedGeneratedProjectAndBackendSql() throws IOException
{
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
ZipOutputStream zip = new ZipOutputStream(outputStream);
addEntry(zip, "demo-backend/pom.xml", "<project />");
addEntry(zip, "demo-backend/sql/demo.sql", "create table demo_backend (id bigint);");
addEntry(zip, "demo-web/package.json", "{\"scripts\":{\"dev\":\"vue-cli-service serve\"}}");
addEntry(zip, "sql/demo.sql", "create table demo_root (id bigint);");
zip.close();
return outputStream.toByteArray();
}
private byte[] zipWithViteGeneratedProject() throws IOException
{
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
ZipOutputStream zip = new ZipOutputStream(outputStream);
addEntry(zip, "demo-backend/pom.xml", "<project />");
addEntry(zip, "demo-portal/package.json", "{\"scripts\":{\"dev\":\"vite --host 0.0.0.0\"},\"dependencies\":{\"@vitejs/plugin-vue\":\"5.0.5\",\"vite\":\"5.3.1\",\"vue\":\"3.4.29\"}}");
addEntry(zip, "sql/demo.sql", "create table demo (id bigint);");
zip.close();
return outputStream.toByteArray();
}
private void addEntry(ZipOutputStream zip, String name, String content) throws IOException
{
zip.putNextEntry(new ZipEntry(name));
zip.write(content.getBytes("UTF-8"));
zip.closeEntry();
}
private void setField(String name, Object value) throws Exception
{
Field field = FrontProjectRunPreviewServiceImpl.class.getDeclaredField(name);
field.setAccessible(true);
field.set(runPreviewService, value);
}
private static class FakeProcess extends Process
{
private boolean alive = true;
private boolean destroyed = false;
@Override
public OutputStream getOutputStream()
{
return new ByteArrayOutputStream();
}
@Override
public InputStream getInputStream()
{
return new ByteArrayInputStream(new byte[0]);
}
@Override
public InputStream getErrorStream()
{
return new ByteArrayInputStream(new byte[0]);
}
@Override
public int waitFor()
{
alive = false;
return 0;
}
@Override
public int exitValue()
{
if (alive)
{
throw new IllegalThreadStateException();
}
return 0;
}
@Override
public void destroy()
{
alive = false;
destroyed = true;
}
@Override
public boolean isAlive()
{
return alive;
}
}
private static class RecordingProcessKiller extends RunPreviewProcessKiller
{
private final List<Integer> stoppedPorts = new ArrayList<Integer>();
@Override
public void stop(Process process, Integer port)
{
if (process != null)
{
process.destroy();
}
if (port != null)
{
stoppedPorts.add(port);
}
}
}
}