diff --git a/core/pom.xml b/core/pom.xml index f9192e4ce..45f2e46da 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -197,6 +197,10 @@ com.google.protobuf protobuf-java-util + + org.eclipse.jgit + org.eclipse.jgit + diff --git a/core/src/main/java/org/jsmart/zerocode/core/constants/ZeroCodeReportConstants.java b/core/src/main/java/org/jsmart/zerocode/core/constants/ZeroCodeReportConstants.java index c296ddc21..8545e65d8 100644 --- a/core/src/main/java/org/jsmart/zerocode/core/constants/ZeroCodeReportConstants.java +++ b/core/src/main/java/org/jsmart/zerocode/core/constants/ZeroCodeReportConstants.java @@ -8,7 +8,9 @@ public interface ZeroCodeReportConstants { String TARGET_REPORT_DIR = "target/zerocode-test-reports/"; String TARGET_FULL_REPORT_CSV_FILE_NAME = "zerocode-junit-granular-report.csv"; String TARGET_FILE_NAME = "target/zerocode-junit-interactive-fuzzy-search.html"; + String SUREFIRE_REPORT_DIR = "target/surefire-reports/"; String HIGH_CHART_HTML_FILE_NAME = "zerocode_results_chart"; + String REPORT_UPLOAD_DIR = "target/upload-reports/"; String AUTHOR_MARKER_OLD = "@@"; //Deprecated String AUTHOR_MARKER_NEW = "@"; String CATEGORY_MARKER = "#"; diff --git a/core/src/main/java/org/jsmart/zerocode/core/di/main/ApplicationMainModule.java b/core/src/main/java/org/jsmart/zerocode/core/di/main/ApplicationMainModule.java index 0e0cab725..1d5c6ebca 100644 --- a/core/src/main/java/org/jsmart/zerocode/core/di/main/ApplicationMainModule.java +++ b/core/src/main/java/org/jsmart/zerocode/core/di/main/ApplicationMainModule.java @@ -27,6 +27,8 @@ import org.jsmart.zerocode.core.engine.validators.ZeroCodeValidatorImpl; import org.jsmart.zerocode.core.report.ZeroCodeReportGenerator; import org.jsmart.zerocode.core.report.ZeroCodeReportGeneratorImpl; +import org.jsmart.zerocode.core.reportsupload.ReportUploader; +import org.jsmart.zerocode.core.reportsupload.ReportUploaderImpl; import org.jsmart.zerocode.core.runner.ZeroCodeMultiStepsScenarioRunner; import org.jsmart.zerocode.core.runner.ZeroCodeMultiStepsScenarioRunnerImpl; @@ -68,6 +70,7 @@ public void configure() { bind(ZeroCodeExternalFileProcessor.class).to(ZeroCodeExternalFileProcessorImpl.class); bind(ZeroCodeParameterizedProcessor.class).to(ZeroCodeParameterizedProcessorImpl.class); bind(ZeroCodeSorter.class).to(ZeroCodeSorterImpl.class); + bind(ReportUploader.class).to(ReportUploaderImpl.class); // ------------------------------------------------ // Bind properties for localhost, CI, DIT, SIT etc diff --git a/core/src/main/java/org/jsmart/zerocode/core/engine/listener/TestUtilityListener.java b/core/src/main/java/org/jsmart/zerocode/core/engine/listener/TestUtilityListener.java index 96142ae7a..e543dc538 100644 --- a/core/src/main/java/org/jsmart/zerocode/core/engine/listener/TestUtilityListener.java +++ b/core/src/main/java/org/jsmart/zerocode/core/engine/listener/TestUtilityListener.java @@ -3,6 +3,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.google.inject.Inject; import org.jsmart.zerocode.core.report.ZeroCodeReportGenerator; +import org.jsmart.zerocode.core.reportsupload.ReportUploader; import org.junit.runner.Description; import org.junit.runner.Result; import org.junit.runner.notification.RunListener; @@ -16,10 +17,13 @@ public class TestUtilityListener extends RunListener { private final ZeroCodeReportGenerator reportGenerator; + private final ReportUploader reportUploader; + @Inject - public TestUtilityListener(ObjectMapper mapper, ZeroCodeReportGenerator injectedReportGenerator) { + public TestUtilityListener(ObjectMapper mapper, ZeroCodeReportGenerator injectedReportGenerator, ReportUploader injectedReportUploader) { this.mapper = mapper; this.reportGenerator = injectedReportGenerator; + this.reportUploader = injectedReportUploader; } @Override @@ -50,9 +54,7 @@ private void printTestCompleted() { * Override this to handle post-finished tasks */ public void runPostFinished() { - /* - * Do nothing for now - */ + uploadReports(); } private void generateChartsAndReports() { @@ -71,4 +73,9 @@ private void generateChartsAndReports() { reportGenerator.generateExtentReport(); } + + private void uploadReports(){ + reportUploader.uploadReport(); + } + } diff --git a/core/src/main/java/org/jsmart/zerocode/core/reportsupload/ReportUploader.java b/core/src/main/java/org/jsmart/zerocode/core/reportsupload/ReportUploader.java new file mode 100644 index 000000000..19e838062 --- /dev/null +++ b/core/src/main/java/org/jsmart/zerocode/core/reportsupload/ReportUploader.java @@ -0,0 +1,5 @@ +package org.jsmart.zerocode.core.reportsupload; + +public interface ReportUploader { + void uploadReport(); +} diff --git a/core/src/main/java/org/jsmart/zerocode/core/reportsupload/ReportUploaderImpl.java b/core/src/main/java/org/jsmart/zerocode/core/reportsupload/ReportUploaderImpl.java new file mode 100644 index 000000000..ecba2605b --- /dev/null +++ b/core/src/main/java/org/jsmart/zerocode/core/reportsupload/ReportUploaderImpl.java @@ -0,0 +1,200 @@ +package org.jsmart.zerocode.core.reportsupload; + +import com.google.inject.Inject; +import com.google.inject.name.Named; +import org.eclipse.jgit.api.Git; +import org.eclipse.jgit.api.errors.GitAPIException; +import org.eclipse.jgit.transport.URIish; +import org.eclipse.jgit.transport.UsernamePasswordCredentialsProvider; +import org.jsmart.zerocode.core.constants.ZeroCodeReportConstants; +import org.jsmart.zerocode.core.report.ZeroCodeReportGeneratorImpl; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.File; +import java.io.IOException; +import java.net.URISyntaxException; +import java.nio.file.*; + +public class ReportUploaderImpl implements ReportUploader { + + private static final Logger LOGGER = LoggerFactory.getLogger(ZeroCodeReportGeneratorImpl.class); + + @Inject(optional = true) + @Named("reports.repo") + private String reportsRepo; + + @Inject(optional = true) + @Named("reports.repo.username") + private String reportsRepoUsername; + + @Inject(optional = true) + @Named("reports.repo.token") + private String reportsRepoToken; + + @Inject(optional = true) + @Named("reports.repo.max.upload.limit.mb") + private Integer reportsRepoMaxUploadLimitMb; + + + public void uploadReport() { + if (!isAllRequiredVariablesSet()) { + LOGGER.warn("One or more required variables are not set. Skipping report upload."); + return; + } + + setDefaultUploadLimit(); + createParentDirectoryIfNotExists(new File(ZeroCodeReportConstants.REPORT_UPLOAD_DIR)); + + try (Git git = initializeOrOpenGitRepository(ZeroCodeReportConstants.REPORT_UPLOAD_DIR)) { + addRemoteRepositoryIfMissing(git); + + //Copy files to the repository + copyFile(ZeroCodeReportConstants.TARGET_FILE_NAME, ZeroCodeReportConstants.REPORT_UPLOAD_DIR); + copyFile( + ZeroCodeReportConstants.TARGET_FULL_REPORT_DIR + ZeroCodeReportConstants.TARGET_FULL_REPORT_CSV_FILE_NAME, + ZeroCodeReportConstants.REPORT_UPLOAD_DIR); + + copyFirstFileUnder2MBMatchingPattern( + ZeroCodeReportConstants.SUREFIRE_REPORT_DIR, + "*.xml", + ZeroCodeReportConstants.REPORT_UPLOAD_DIR + ); + copyFirstFileUnder2MBMatchingPattern( + ZeroCodeReportConstants.TARGET_FULL_REPORT_DIR + "/logs", + "*.log", + ZeroCodeReportConstants.REPORT_UPLOAD_DIR + ); + addAndCommitChanges(git); + pushToRemoteRepository(git); + } catch (Exception e) { + LOGGER.warn("Report upload failed: {}", e.getMessage()); + } + } + + protected void addRemoteRepositoryIfMissing(Git git) throws URISyntaxException, GitAPIException { + if (git.remoteList().call().isEmpty()) { + LOGGER.debug("Adding remote repository: {}", reportsRepo); + git.remoteAdd().setName("origin").setUri(new URIish(reportsRepo)).call(); + } else { + LOGGER.debug("Remote repository already exists."); + } + } + + protected void addAndCommitChanges(Git git) throws GitAPIException { + git.add().addFilepattern(".").call(); + LOGGER.debug("Added all files to the Git index."); + + git.commit() + .setMessage("Updated files") + .setAuthor(reportsRepoUsername, reportsRepoUsername) + .call(); + LOGGER.debug("Committed changes."); + } + + protected void pushToRemoteRepository(Git git) throws GitAPIException { + git.push() + .setCredentialsProvider(new UsernamePasswordCredentialsProvider(reportsRepoUsername, reportsRepoToken)) + .call(); + LOGGER.debug("Pushed changes to remote repository!"); + } + + protected boolean isAllRequiredVariablesSet() { + return reportsRepo != null && !reportsRepo.isEmpty() && + reportsRepoUsername != null && !reportsRepoUsername.isEmpty() && + reportsRepoToken != null && !reportsRepoToken.isEmpty(); + } + + protected void setDefaultUploadLimit() { + if (reportsRepoMaxUploadLimitMb == null) { + reportsRepoMaxUploadLimitMb = 2; + LOGGER.debug("reportsRepoMaxUploadLimitMb is not set. Defaulting to 2 MB."); + } + } + + protected void createParentDirectoryIfNotExists(File repoDir) { + File parentDir = repoDir.getParentFile(); + if (!parentDir.exists() && parentDir.mkdirs()) { + LOGGER.debug("Directory created: {}", parentDir.getAbsolutePath()); + } else if (!parentDir.exists()) { + LOGGER.warn("Failed to create directory: {}", parentDir.getAbsolutePath()); + } + } + + protected Git initializeOrOpenGitRepository(String repoUploadDir) throws IOException, GitAPIException { + if (new File(repoUploadDir, ".git").exists()) { + LOGGER.debug("Existing Git repository found."); + Git git = Git.open(new File(repoUploadDir)); + LOGGER.debug("Pulling latest changes from remote repository..."); + git.pull() + .setCredentialsProvider(new UsernamePasswordCredentialsProvider(reportsRepoUsername, reportsRepoToken)) + .call(); + return git; + } else { + LOGGER.debug("Initializing a new Git repository..."); + return Git.cloneRepository() + .setURI(reportsRepo) + .setDirectory(new File(repoUploadDir)) + .setCredentialsProvider(new UsernamePasswordCredentialsProvider(reportsRepoUsername, reportsRepoToken)) + .call(); + } + } + + protected void copyFile(String sourcePath, String targetDirPath) throws IOException { + File sourceFile = new File(sourcePath); + if (!sourceFile.exists()) { + LOGGER.warn("File not found: {}", sourcePath); + return; + } + if (sourceFile.length() <= reportsRepoMaxUploadLimitMb * 1024 * 1024) { + Files.copy(sourceFile.toPath(), Paths.get(targetDirPath, sourceFile.getName()), StandardCopyOption.REPLACE_EXISTING); + LOGGER.debug("File copied: {}", sourceFile.getName()); + } else { + LOGGER.warn("File size exceeds {} MB. Skipping copy: {}", reportsRepoMaxUploadLimitMb, sourceFile.getName()); + } + } + + protected void copyFirstFileUnder2MBMatchingPattern(String dirPath, String globPattern, String targetDirPath) throws IOException { + Path dir = Paths.get(dirPath); + + if (!Files.exists(dir) || !Files.isDirectory(dir)) { + LOGGER.warn("Directory does not exist or is not a valid directory: {}", dirPath); + return; + } + final long maxSizeInBytes = reportsRepoMaxUploadLimitMb * 1024 * 1024; + + try (DirectoryStream stream = Files.newDirectoryStream(dir, globPattern)) { + for (Path entry : stream) { + if (Files.isRegularFile(entry) && Files.size(entry) <= maxSizeInBytes) { + Path target = Paths.get(targetDirPath, entry.getFileName().toString()); + Files.copy(entry, target, StandardCopyOption.REPLACE_EXISTING); + LOGGER.info("Copied first matched file: {}", entry.getFileName()); + return; + } + } + LOGGER.warn("No file under 2MB found matching pattern: {}", globPattern); + } + } + + + public void setReportsRepo(String reportsRepo) { + this.reportsRepo = reportsRepo; + } + + public void setReportsRepoUsername(String reportsRepoUsername) { + this.reportsRepoUsername = reportsRepoUsername; + } + + public void setReportsRepoToken(String reportsRepoToken) { + this.reportsRepoToken = reportsRepoToken; + } + + public void setReportsRepoMaxUploadLimitMb(Integer reportsRepoMaxUploadLimitMb) { + if (reportsRepoMaxUploadLimitMb == null) { + this.reportsRepoMaxUploadLimitMb = 2; + }else { + this.reportsRepoMaxUploadLimitMb = reportsRepoMaxUploadLimitMb; + } + + } +} diff --git a/core/src/test/java/org/jsmart/zerocode/core/reportsupload/ReportUploaderImplTest.java b/core/src/test/java/org/jsmart/zerocode/core/reportsupload/ReportUploaderImplTest.java new file mode 100644 index 000000000..0d19b39e3 --- /dev/null +++ b/core/src/test/java/org/jsmart/zerocode/core/reportsupload/ReportUploaderImplTest.java @@ -0,0 +1,126 @@ +package org.jsmart.zerocode.core.reportsupload; + +import com.google.inject.AbstractModule; +import com.google.inject.Inject; +import com.google.inject.name.Named; +import org.eclipse.jgit.api.Git; +import org.jsmart.zerocode.core.di.main.ApplicationMainModule; +import org.jsmart.zerocode.core.guice.ZeroCodeGuiceTestRule; +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +public class ReportUploaderImplTest { + private ReportUploaderImpl reportUploader; + private Path tempDir; + + @Inject(optional = true) + @Named("reports.repo") + private String reportsRepo; + + @Inject(optional = true) + @Named("reports.repo.username") + private String reportsRepoUsername; + + @Inject(optional = true) + @Named("reports.repo.token") + private String reportsRepoToken; + + @Inject(optional = true) + @Named("reports.repo.max.upload.limit.mb") + private Integer reportsRepoMaxUploadLimitMb; + + @Rule + public ZeroCodeGuiceTestRule guiceRule = new ZeroCodeGuiceTestRule(this, ReportUploaderImplTest.ZeroCodeTestModule.class); + + public static class ZeroCodeTestModule extends AbstractModule { + @Override + protected void configure() { + ApplicationMainModule applicationMainModule = new ApplicationMainModule("report_uploader.properties"); + install(applicationMainModule); + } + } + + + @Before + public void setUp() throws IOException { + reportUploader = new ReportUploaderImpl(); + reportUploader.setDefaultUploadLimit(); + reportUploader.setReportsRepo(reportsRepo); + reportUploader.setReportsRepoUsername(reportsRepoUsername); + reportUploader.setReportsRepoToken(reportsRepoToken); + reportUploader.setReportsRepoMaxUploadLimitMb(reportsRepoMaxUploadLimitMb); + tempDir = Files.createTempDirectory("testRepo"); + } + + @Test + public void testInitializeOrOpenGitRepository_cloneRepo() throws Exception { + if(reportUploader.isAllRequiredVariablesSet()){ + File targetDir = new File(tempDir.toFile(),"clonerepo"); + Git git = reportUploader.initializeOrOpenGitRepository(targetDir.getAbsolutePath()); + assertTrue(new File(targetDir, ".git").exists()); + git.close(); + } + } + + @Test + public void testAddAndCommitChanges() throws Exception { + if(reportUploader.isAllRequiredVariablesSet()){ + File targetDir = new File(tempDir.toFile(),"commitrepo"); + Git git = Git.init().setDirectory(targetDir).call(); + + // Create a dummy file to commit + File file = new File(targetDir, "dummy.txt"); + Files.write(file.toPath(), "data".getBytes()); + + reportUploader.addAndCommitChanges(git); + assertTrue(git.log().call().iterator().hasNext()); + git.close(); + } + } + + @Test + public void copyFile_fileExistsAndWithinSizeLimit_copiesFile() throws IOException { + System.out.println(reportsRepo); + File sourceFile = new File(tempDir.toFile(), "source.txt"); + Files.write(sourceFile.toPath(), "test content".getBytes()); + File targetDir = new File(tempDir.toFile(), "target"); + targetDir.mkdir(); + + reportUploader.copyFile(sourceFile.getAbsolutePath(), targetDir.getAbsolutePath()); + + File copiedFile = new File(targetDir, sourceFile.getName()); + assertTrue(copiedFile.exists()); + } + + @Test + public void copyFile_fileExceedsSizeLimit_logsWarning() throws IOException { + File sourceFile = new File(tempDir.toFile(), "largeFile.txt"); + Files.write(sourceFile.toPath(), new byte[3 * 1024 * 1024]); // 3 MB file + File targetDir = new File(tempDir.toFile(), "target"); + targetDir.mkdir(); + reportUploader.copyFile(sourceFile.getAbsolutePath(), targetDir.getAbsolutePath()); + + File copiedFile = new File(targetDir, sourceFile.getName()); + assertFalse(copiedFile.exists()); + } + + @After + public void tearDown() throws IOException { + if (tempDir != null) { + Files.walk(tempDir) + .map(Path::toFile) + .forEach(File::delete); + } + } + +} diff --git a/core/src/test/resources/report_uploader.properties b/core/src/test/resources/report_uploader.properties new file mode 100644 index 000000000..654e12cb1 --- /dev/null +++ b/core/src/test/resources/report_uploader.properties @@ -0,0 +1,4 @@ +reports.repo= +reports.repo.username= +reports.repo.token= +reports.repo.max.upload.limit.mb= \ No newline at end of file diff --git a/http-testing-examples/src/test/java/org/jsmart/zerocode/testhelp/tests/reportuploader/ReportUploaderTest.java b/http-testing-examples/src/test/java/org/jsmart/zerocode/testhelp/tests/reportuploader/ReportUploaderTest.java new file mode 100644 index 000000000..988cd687f --- /dev/null +++ b/http-testing-examples/src/test/java/org/jsmart/zerocode/testhelp/tests/reportuploader/ReportUploaderTest.java @@ -0,0 +1,19 @@ +package org.jsmart.zerocode.testhelp.tests.reportuploader; + +import org.jsmart.zerocode.core.domain.Scenario; +import org.jsmart.zerocode.core.domain.TargetEnv; +import org.jsmart.zerocode.core.runner.ZeroCodeUnitRunner; +import org.junit.Test; +import org.junit.runner.RunWith; + +import static org.junit.Assert.assertTrue; + +@TargetEnv("report_uploader_test.properties") +@RunWith(ZeroCodeUnitRunner.class) +public class ReportUploaderTest { + + @Test + public void testGet() throws Exception { + assertTrue(true); + } +} diff --git a/http-testing-examples/src/test/resources/report_uploader_test.properties b/http-testing-examples/src/test/resources/report_uploader_test.properties new file mode 100644 index 000000000..654e12cb1 --- /dev/null +++ b/http-testing-examples/src/test/resources/report_uploader_test.properties @@ -0,0 +1,4 @@ +reports.repo= +reports.repo.username= +reports.repo.token= +reports.repo.max.upload.limit.mb= \ No newline at end of file diff --git a/pom.xml b/pom.xml index 1ad3402e2..e984b4659 100644 --- a/pom.xml +++ b/pom.xml @@ -110,6 +110,7 @@ false 3.24.4 + 5.13.0.202109080827-r @@ -333,6 +334,11 @@ protobuf-java-util ${google.protobuf.version} + + org.eclipse.jgit + org.eclipse.jgit + ${eclipse.jgit.version} +